/**
  * Check the undo limit before adding an operation. Return a boolean indicating whether the undo
  * should proceed.
  */
 private boolean checkUndoLimit(IUndoableOperation operation) {
   IUndoContext[] contexts = operation.getContexts();
   for (int i = 0; i < contexts.length; i++) {
     int limit = getLimit(contexts[i]);
     if (limit > 0) {
       forceUndoLimit(contexts[i], limit - 1);
     } else {
       // this context has a 0 limit
       operation.removeContext(contexts[i]);
     }
   }
   return operation.getContexts().length > 0;
 }
 @Override
 public void setLimit(IUndoContext context, int limit) {
   Assert.isTrue(limit >= 0);
   /*
    * The limit checking methods interpret a null context as a global limit
    * to be enforced. We do not wish to support a global limit in this
    * implementation, so we throw an exception for a null context. The rest
    * of the implementation can handle a null context, so subclasses can
    * override this if a global limit is desired.
    */
   Assert.isNotNull(context);
   limits.put(context, new Integer(limit));
   synchronized (undoRedoHistoryLock) {
     forceUndoLimit(context, limit);
     forceRedoLimit(context, limit);
   }
 }
  @Override
  public void replaceOperation(IUndoableOperation operation, IUndoableOperation[] replacements) {
    // check the undo history first.
    boolean inUndo = false;
    synchronized (undoRedoHistoryLock) {
      int index = undoList.indexOf(operation);
      if (index > -1) {
        inUndo = true;
        undoList.remove(operation);
        // notify listeners after the lock on undoList is released
        ArrayList<IUndoContext> allContexts = new ArrayList<>(replacements.length);
        for (int i = 0; i < replacements.length; i++) {
          IUndoContext[] opContexts = replacements[i].getContexts();
          for (int j = 0; j < opContexts.length; j++) {
            allContexts.add(opContexts[j]);
          }
          undoList.add(index, replacements[i]);
          // notify listeners after the lock on the history is
          // released
        }
        // recheck all the limits. We do this at the end so the index
        // doesn't change during replacement
        for (int i = 0; i < allContexts.size(); i++) {
          IUndoContext context = allContexts.get(i);
          forceUndoLimit(context, getLimit(context));
        }
      }
    }
    if (inUndo) {
      // notify listeners of operations added and removed
      internalRemove(operation);
      for (int i = 0; i < replacements.length; i++) {
        notifyAdd(replacements[i]);
      }
      return;
    }

    // operation was not in the undo history. Check the redo history.

    synchronized (undoRedoHistoryLock) {
      int index = redoList.indexOf(operation);
      if (index == -1) {
        return;
      }
      ArrayList<IUndoContext> allContexts = new ArrayList<>(replacements.length);
      redoList.remove(operation);
      // notify listeners after we release the lock on redoList
      for (int i = 0; i < replacements.length; i++) {
        IUndoContext[] opContexts = replacements[i].getContexts();
        for (int j = 0; j < opContexts.length; j++) {
          allContexts.add(opContexts[j]);
        }
        redoList.add(index, replacements[i]);
        // notify listeners after we release the lock on redoList
      }
      // recheck all the limits. We do this at the end so the index
      // doesn't change during replacement
      for (int i = 0; i < allContexts.size(); i++) {
        IUndoContext context = allContexts.get(i);
        forceRedoLimit(context, getLimit(context));
      }
    }
    // send listener notifications after we release the lock on the history
    internalRemove(operation);
    for (int i = 0; i < replacements.length; i++) {
      notifyAdd(replacements[i]);
    }
  }