/*
   * Flush the undo stack of all operations that have the given context.
   */
  private void flushUndo(IUndoContext context) {
    if (DEBUG_OPERATION_HISTORY_DISPOSE) {
      Tracing.printTrace(
          "OPERATIONHISTORY", "Flushing undo history for " + context); // $NON-NLS-1$ //$NON-NLS-2$
    }

    synchronized (undoRedoHistoryLock) {

      // Get all operations that have the context (or one that matches)
      Object[] filtered = filter(undoList, context);
      for (int i = 0; i < filtered.length; i++) {
        IUndoableOperation operation = (IUndoableOperation) filtered[i];
        if (context == GLOBAL_UNDO_CONTEXT || operation.getContexts().length == 1) {
          // remove the operation if it only has the context or we are
          // flushing all
          undoList.remove(operation);
          internalRemove(operation);
        } else {
          // remove the reference to the context.
          // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786
          // It is not enough to simply remove the context. There could
          // be one or more contexts that match the one we are trying to
          // dispose.
          IUndoContext[] contexts = operation.getContexts();
          for (int j = 0; j < contexts.length; j++) {
            if (contexts[j].matches(context)) {
              operation.removeContext(contexts[j]);
            }
          }
          if (operation.getContexts().length == 0) {
            undoList.remove(operation);
            internalRemove(operation);
          }
        }
      }
    }
    /*
     * There may be an open composite. If it has this context, then the
     * context must be removed. If it has only this context or we are
     * flushing all operations, then null it out and notify that we are
     * ending it. We don't remove it since it was never added.
     */
    ICompositeOperation endedComposite = null;
    synchronized (openCompositeLock) {
      if (openComposite != null) {
        if (openComposite.hasContext(context)) {
          if (context == GLOBAL_UNDO_CONTEXT || openComposite.getContexts().length == 1) {
            endedComposite = openComposite;
            openComposite = null;
          } else {
            openComposite.removeContext(context);
          }
        }
      }
    }
    // notify outside of the synchronized block.
    if (endedComposite != null) {
      notifyNotOK(endedComposite);
    }
  }
  /*
   * Flush the redo stack of all operations that have the given context.
   */
  private void flushRedo(IUndoContext context) {
    if (DEBUG_OPERATION_HISTORY_DISPOSE) {
      Tracing.printTrace(
          "OPERATIONHISTORY", "Flushing redo history for " + context); // $NON-NLS-1$ //$NON-NLS-2$
    }

    synchronized (undoRedoHistoryLock) {
      Object[] filtered = filter(redoList, context);
      for (int i = 0; i < filtered.length; i++) {
        IUndoableOperation operation = (IUndoableOperation) filtered[i];
        if (context == GLOBAL_UNDO_CONTEXT || operation.getContexts().length == 1) {
          // remove the operation if it only has the context or we are
          // flushing all
          redoList.remove(operation);
          internalRemove(operation);
        } else {
          // remove the reference to the context.
          // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786
          // It is not enough to simply remove the context. There could
          // be one or more contexts that match the one we are trying to
          // dispose.
          IUndoContext[] contexts = operation.getContexts();
          for (int j = 0; j < contexts.length; j++) {
            if (contexts[j].matches(context)) {
              operation.removeContext(contexts[j]);
            }
          }
          if (operation.getContexts().length == 0) {
            redoList.remove(operation);
            internalRemove(operation);
          }
        }
      }
    }
  }
 /**
  * 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;
 }
 /*
  * Force the undo history for the given context to contain max or less
  * items.
  */
 private void forceUndoLimit(IUndoContext context, int max) {
   synchronized (undoRedoHistoryLock) {
     Object[] filtered = filter(undoList, context);
     int size = filtered.length;
     if (size > 0) {
       int index = 0;
       while (size > max) {
         IUndoableOperation removed = (IUndoableOperation) filtered[index];
         if (context == GLOBAL_UNDO_CONTEXT || removed.getContexts().length == 1) {
           /*
            * remove the operation if we are enforcing a global limit
            * or if the operation only has the specified context
            */
           undoList.remove(removed);
           internalRemove(removed);
         } else {
           /*
            * if the operation has multiple contexts and we've reached
            * the limit for only one of them, then just remove the
            * context, not the operation.
            */
           removed.removeContext(context);
         }
         size--;
         index++;
       }
     }
   }
 }
  @Override
  public void add(IUndoableOperation operation) {
    Assert.isNotNull(operation);

    /*
     * If we are in the middle of executing an open batching operation, and
     * this is not that operation, then we need only add the context of the
     * new operation to the batch. The operation itself is disposed since we
     * will never undo or redo it. We consider it to be triggered by the
     * batching operation and assume that its undo will be triggered by the
     * batching operation undo.
     */
    synchronized (openCompositeLock) {
      if (openComposite != null && openComposite != operation) {
        openComposite.add(operation);
        return;
      }
    }

    if (checkUndoLimit(operation)) {
      synchronized (undoRedoHistoryLock) {
        undoList.add(operation);
      }
      notifyAdd(operation);

      // flush redo stack for related contexts
      IUndoContext[] contexts = operation.getContexts();
      for (int i = 0; i < contexts.length; i++) {
        flushRedo(contexts[i]);
      }
    } else {
      // Dispose the operation since we will not have a reference to it.
      operation.dispose();
    }
  }
 /*
  * (non-Javadoc)
  *
  * @see org.eclipse.core.commands.operations.IOperationApprover#proceedRedoing(org.eclipse.core.commands.operations.IUndoableOperation,
  *      org.eclipse.core.commands.operations.IOperationHistory,
  *      org.eclipse.core.runtime.IAdaptable)
  */
 public final IStatus proceedRedoing(
     IUndoableOperation operation, IOperationHistory history, IAdaptable info) {
   IUndoContext[] contexts = operation.getContexts();
   for (int i = 0; i < contexts.length; i++) {
     IUndoContext context = contexts[i];
     if (history.getRedoOperation(context) != operation) {
       IStatus status = allowLinearRedoViolation(operation, context, history, info);
       if (!status.isOK()) return status;
     }
   }
   return Status.OK_STATUS;
 }