/**
   * Perform the redo. All validity checks have already occurred.
   *
   * @param monitor
   * @param operation
   */
  private IStatus doRedo(IProgressMonitor monitor, IAdaptable info, IUndoableOperation operation)
      throws ExecutionException {

    IStatus status = getRedoApproval(operation, info);
    if (status.isOK()) {
      notifyAboutToRedo(operation);
      try {
        status = operation.redo(monitor, info);
      } catch (OperationCanceledException e) {
        status = Status.CANCEL_STATUS;
      } catch (ExecutionException e) {
        notifyNotOK(operation);
        if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
          Tracing.printTrace(
              "OPERATIONHISTORY", //$NON-NLS-1$
              "ExecutionException while redoing " + operation); // $NON-NLS-1$
        }
        throw e;
      } catch (Exception e) {
        notifyNotOK(operation);
        if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
          Tracing.printTrace(
              "OPERATIONHISTORY", //$NON-NLS-1$
              "Exception while redoing " + operation); // $NON-NLS-1$
        }
        throw new ExecutionException(
            "While redoing the operation, an exception occurred", e); // $NON-NLS-1$
      }
    }

    // if successful, the operation is removed from the redo history and
    // placed back in the undo history.
    if (status.isOK()) {
      boolean addedToUndo = true;
      synchronized (undoRedoHistoryLock) {
        redoList.remove(operation);
        if (checkUndoLimit(operation)) {
          undoList.add(operation);
        } else {
          addedToUndo = false;
        }
      }
      // dispose the operation since we could not add it to the
      // stack and will no longer have a reference to it.
      if (!addedToUndo) {
        operation.dispose();
      }

      // notify listeners must happen after history is updated
      notifyRedone(operation);
    } else {
      notifyNotOK(operation, status);
    }

    return status;
  }
  /*
   * 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);
    }
  }
  @Override
  public void closeOperation(boolean operationOK, boolean addToHistory, int mode) {
    ICompositeOperation endedComposite = null;

    synchronized (openCompositeLock) {
      if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
        if (openComposite == null) {
          Tracing.printTrace(
              "OPERATIONHISTORY",
              "Attempted to close operation when none was open"); //$NON-NLS-1$//$NON-NLS-2$
          return;
        }
      }
      // notifications will occur outside the synchonized block
      if (openComposite != null) {
        if (DEBUG_OPERATION_HISTORY_OPENOPERATION) {
          Tracing.printTrace(
              "OPERATIONHISTORY",
              "Closing operation " + openComposite); // $NON-NLS-1$ //$NON-NLS-2$
        }
        endedComposite = openComposite;
        openComposite = null;
      }
    }
    // any mode other than EXECUTE was triggered by a request to undo or
    // redo something already in the history, so undo and redo
    // notification will occur at the end of that sequence.
    if (endedComposite != null) {
      if (operationOK) {
        if (mode == EXECUTE) {
          notifyDone(endedComposite);
        }
        if (addToHistory) {
          add(endedComposite);
        }
      } else {
        if (mode == EXECUTE) {
          notifyNotOK(endedComposite);
        }
      }
    }
  }
 /*
  * Notify listeners that an operation did not succeed after an attempt to
  * execute, undo, or redo was made.
  */
 private void notifyNotOK(IUndoableOperation operation) {
   notifyNotOK(operation, null);
 }
  @Override
  public IStatus execute(IUndoableOperation operation, IProgressMonitor monitor, IAdaptable info)
      throws ExecutionException {
    Assert.isNotNull(operation);

    // error if operation is invalid
    if (!operation.canExecute()) {
      return IOperationHistory.OPERATION_INVALID_STATUS;
    }

    // check with the operation approvers
    IStatus status = getExecuteApproval(operation, info);
    if (!status.isOK()) {
      // not approved. No notifications are sent, just return the status.
      return status;
    }

    /*
     * If we are in the middle of an open composite, then we will add this
     * operation to the open operation rather than add the operation to the
     * history. We will still execute it.
     */
    boolean merging = false;
    synchronized (openCompositeLock) {
      if (openComposite != null) {
        // the composite shouldn't be executed explicitly while it is
        // still
        // open
        if (openComposite == operation) {
          return IOperationHistory.OPERATION_INVALID_STATUS;
        }
        openComposite.add(operation);
        merging = true;
      }
    }

    /*
     * Execute the operation
     */
    if (!merging) {
      notifyAboutToExecute(operation);
    }
    try {
      status = operation.execute(monitor, info);
    } catch (OperationCanceledException e) {
      status = Status.CANCEL_STATUS;
    } catch (ExecutionException e) {
      notifyNotOK(operation);
      throw e;
    } catch (Exception e) {
      notifyNotOK(operation);
      throw new ExecutionException(
          "While executing the operation, an exception occurred", e); // $NON-NLS-1$
    }

    // if successful, the notify listeners are notified and the operation is
    // added to the history
    if (!merging) {
      if (status.isOK()) {
        notifyDone(operation);
        add(operation);
      } else {
        notifyNotOK(operation, status);
        // dispose the operation since we did not add it to the stack
        // and will no longer have a reference to it.
        operation.dispose();
      }
    }
    // all other severities are not interpreted. Simply return the status.
    return status;
  }