Package org.eclipse.ui.internal.handlers

Source Code of org.eclipse.ui.internal.handlers.HandlerAuthority

/*******************************************************************************
* Copyright (c) 2005, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*******************************************************************************/

package org.eclipse.ui.internal.handlers;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.util.Tracing;
import org.eclipse.core.expressions.EvaluationContext;
import org.eclipse.core.expressions.EvaluationResult;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ISources;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.Policy;
import org.eclipse.ui.internal.services.EvaluationResultCacheComparator;
import org.eclipse.ui.internal.services.ExpressionAuthority;

/**
* <p>
* A central authority for resolving conflicts between handlers. This authority
* listens to a variety of incoming sources, and updates the underlying commands
* if changes in the active handlers occur.
* </p>
* <p>
* This authority encapsulates all of the handler conflict resolution mechanisms
* for the workbench. A conflict occurs if two or more handlers are assigned to
* the same command identifier. To resolve this conflict, the authority
* considers which source the handler came from.
* </p>
*
* @since 3.1
*/
final class HandlerAuthority extends ExpressionAuthority {

  /**
   * The default size of the set containing the activations to recompute. This
   * is more than enough to cover the average case.
   */
  private static final int ACTIVATIONS_BY_SOURCE_SIZE = 256;

  /**
   * The default size of the set containing the activations to recompute. This
   * is more than enough to cover the average case.
   */
  private static final int ACTIVATIONS_TO_RECOMPUTE_SIZE = 1024;

  /**
   * Whether the workbench command support should kick into debugging mode.
   * This causes the unresolvable handler conflicts to be printed to the
   * console.
   */
  private static final boolean DEBUG = Policy.DEBUG_HANDLERS;

  /**
   * Whether the performance information should be printed about the
   * performance of the handler authority.
   */
  private static final boolean DEBUG_PERFORMANCE = Policy.DEBUG_HANDLERS_PERFORMANCE;

  /**
   * Whether the workbench command support should kick into verbose debugging
   * mode. This causes the resolvable handler conflicts to be printed to the
   * console.
   */
  private static final boolean DEBUG_VERBOSE = Policy.DEBUG_HANDLERS
      && Policy.DEBUG_HANDLERS_VERBOSE;

  /**
   * The command identifier to which the verbose output should be restricted.
   */
  private static final String DEBUG_VERBOSE_COMMAND_ID = Policy.DEBUG_HANDLERS_VERBOSE_COMMAND_ID;

  /**
   * The component name to print when displaying tracing information.
   */
  private static final String TRACING_COMPONENT = "HANDLERS"; //$NON-NLS-1$

  /**
   * A bucket sort of the handler activations based on source priority of its
   * expression. Each expression will appear only once per set, but may appear
   * in multiple sets. If no activations are defined for a particular priority
   * level, then the array at that index will only contain <code>null</code>.
   * This is an array of {@link Map}, where the maps contain instances of
   * {@link Collection} containing instances of {@link IHandlerActivation}
   * indexed by instances of {@link Expression}.
   */
  private final Map[] activationsByExpressionBySourcePriority = new Map[33];

  /**
   * The command service that should be updated when the handlers are
   * changing. This value is never <code>null</code>.
   */
  private final ICommandService commandService;

  /**
   * This is a map of handler activations (<code>SortedSet</code> of
   * <code>IHandlerActivation</code>) sorted by command identifier (<code>String</code>).
   * If there is only one handler activation for a command, then the
   * <code>SortedSet</code> is replaced by a <code>IHandlerActivation</code>.
   * If there is no activation, the entry should be removed entirely.
   */
  private final Map handlerActivationsByCommandId = new HashMap();

  private Set previousLogs = new HashSet();

  /**
   * Constructs a new instance of <code>HandlerAuthority</code>.
   *
   * @param commandService
   *            The command service from which commands can be retrieved (to
   *            update their handlers); must not be <code>null</code>.
   */
  HandlerAuthority(final ICommandService commandService) {
    if (commandService == null) {
      throw new NullPointerException(
          "The handler authority needs a command service"); //$NON-NLS-1$
    }

    this.commandService = commandService;
  }

  /**
   * Activates a handler on the workbench. This will add it to a master list.
   * If conflicts exist, they will be resolved based on the source priority.
   * If conflicts still exist, then no handler becomes active.
   *
   * @param activation
   *            The activation; must not be <code>null</code>.
   */
  final void activateHandler(final IHandlerActivation activation) {
    // First we update the handlerActivationsByCommandId map.
    final String commandId = activation.getCommandId();
    MultiStatus conflicts = new MultiStatus("org.eclipse.ui.workbench", 0//$NON-NLS-1$
        "A handler conflict occurred.  This may disable some commands.", //$NON-NLS-1$
        null);
    final Object value = handlerActivationsByCommandId.get(commandId);
    if (value instanceof SortedSet) {
      final SortedSet handlerActivations = (SortedSet) value;
      if (!handlerActivations.contains(activation)) {
        handlerActivations.add(activation);
        updateCommand(commandId, resolveConflicts(commandId,
            handlerActivations, conflicts));
      }
    } else if (value instanceof IHandlerActivation) {
      if (value != activation) {
        final SortedSet handlerActivations = new TreeSet(
            new EvaluationResultCacheComparator());
        handlerActivations.add(value);
        handlerActivations.add(activation);
        handlerActivationsByCommandId
            .put(commandId, handlerActivations);
        updateCommand(commandId, resolveConflicts(commandId,
            handlerActivations, conflicts));
      }
    } else {
      handlerActivationsByCommandId.put(commandId, activation);
      updateCommand(commandId, (evaluate(activation) ? activation : null));
    }
   
    if (conflicts.getSeverity()!=IStatus.OK) {
      WorkbenchPlugin.log(conflicts);
    }

    // Next we update the source priority bucket sort of activations.
    final int sourcePriority = activation.getSourcePriority();
    for (int i = 1; i <= 32; i++) {
      if ((sourcePriority & (1 << i)) != 0) {
        Map activationsByExpression = activationsByExpressionBySourcePriority[i];
        if (activationsByExpression == null) {
          activationsByExpression = new HashMap(
              ACTIVATIONS_BY_SOURCE_SIZE);
          activationsByExpressionBySourcePriority[i] = activationsByExpression;
        }

        final Expression expression = activation.getExpression();
        Collection activations = (Collection) activationsByExpression
            .get(expression);
        if (activations == null) {
          activations = new HashSet();
          activationsByExpression.put(expression, activations);
        }
        activations.add(activation);
      }
    }
  }

  /**
   * Removes an activation for a handler on the workbench. This will remove it
   * from the master list, and update the appropriate command, if necessary.
   *
   * @param activation
   *            The activation; must not be <code>null</code>.
   */
  final void deactivateHandler(final IHandlerActivation activation) {
    // First we update the handlerActivationsByCommandId map.
    final String commandId = activation.getCommandId();
    MultiStatus conflicts = new MultiStatus("org.eclipse.ui.workbench", 0//$NON-NLS-1$
        "A handler conflict occurred.  This may disable some commands.", //$NON-NLS-1$
        null);
    final Object value = handlerActivationsByCommandId.get(commandId);
    if (value instanceof SortedSet) {
      final SortedSet handlerActivations = (SortedSet) value;
      if (handlerActivations.contains(activation)) {
        handlerActivations.remove(activation);
        if (handlerActivations.isEmpty()) {
          handlerActivationsByCommandId.remove(commandId);
          updateCommand(commandId, null);

        } else if (handlerActivations.size() == 1) {
          final IHandlerActivation remainingActivation = (IHandlerActivation) handlerActivations
              .iterator().next();
          handlerActivationsByCommandId.put(commandId,
              remainingActivation);
          updateCommand(
              commandId,
              (evaluate(remainingActivation) ? remainingActivation
                  : null));

        } else {
          updateCommand(commandId, resolveConflicts(commandId,
              handlerActivations, conflicts));
        }
      }
    } else if (value instanceof IHandlerActivation) {
      if (value == activation) {
        handlerActivationsByCommandId.remove(commandId);
        updateCommand(commandId, null);
      }
    }
    if (conflicts.getSeverity()!=IStatus.OK) {
      WorkbenchPlugin.log(conflicts);
    }

    // Next we update the source priority bucket sort of activations.
    final int sourcePriority = activation.getSourcePriority();
    for (int i = 1; i <= 32; i++) {
      if ((sourcePriority & (1 << i)) != 0) {
        final Map activationsByExpression = activationsByExpressionBySourcePriority[i];
        if (activationsByExpression == null) {
          continue;
        }

        final Expression expression = activation.getExpression();
        final Collection activations = (Collection) activationsByExpression
            .get(expression);
        activations.remove(activation);
        if (activations.isEmpty()) {
          activationsByExpression.remove(expression);
        }

        if (activationsByExpression.isEmpty()) {
          activationsByExpressionBySourcePriority[i] = null;
        }
      }
    }
  }

  /**
   * Returns the currently active shell.
   *
   * @return The currently active shell; may be <code>null</code>.
   */
  final Shell getActiveShell() {
    return (Shell) getVariable(ISources.ACTIVE_SHELL_NAME);
  }

  /**
   * Resolves conflicts between multiple handlers for the same command
   * identifier. This tries to select the best activation based on the source
   * priority. For the sake of comparison, activations with the same handler
   * are considered equivalent (i.e., non-conflicting).
   *
   * @param commandId
   *            The identifier of the command for which the conflicts should
   *            be detected; must not be <code>null</code>. This is only
   *            used for debugging purposes.
   * @param activations
   *            All of the possible handler activations for the given command
   *            identifier; must not be <code>null</code>.
   * @return The best matching handler activation. If none can be found (e.g.,
   *         because of unresolvable conflicts), then this returns
   *         <code>null</code>.
   */
  private final IHandlerActivation resolveConflicts(final String commandId,
      final SortedSet activations, MultiStatus conflicts) {
    // If we don't have any, then there is no match.
    if (activations.isEmpty()) {
      return null;
    }

    // Cycle over the activations, remembered the current best.
    final Iterator activationItr = activations.iterator();
    IHandlerActivation bestActivation = null;
    IHandlerActivation currentActivation = null;
    boolean conflict = false;
    while (activationItr.hasNext()) {
      currentActivation = (IHandlerActivation) activationItr.next();
      if (!evaluate(currentActivation)) {
        continue; // only consider potentially active handlers
      }

      // Check to see if we haven't found a potentially active handler yet
      if ((DEBUG_VERBOSE)
          && ((DEBUG_VERBOSE_COMMAND_ID == null) || (DEBUG_VERBOSE_COMMAND_ID
              .equals(commandId)))) {
        Tracing.printTrace(TRACING_COMPONENT,
            "    resolveConflicts: eval: " + currentActivation); //$NON-NLS-1$
      }
      if (bestActivation == null) {
        bestActivation = currentActivation;
        conflict = false;
        continue;
      }

      // Compare the two handlers.
      final int comparison = bestActivation.compareTo(currentActivation);
      if (comparison < 0) {
        bestActivation = currentActivation;
        conflict = false;

      } else if (comparison == 0) {
        if (currentActivation.getHandler() != bestActivation
            .getHandler()) {
          conflict = true;
          break;
        }

      } else {
        break;
      }
    }

    // If we are logging information, now is the time to do it.
    if (DEBUG) {
      if (conflict) {
        Tracing.printTrace(TRACING_COMPONENT,
            "Unresolved conflict detected for '" //$NON-NLS-1$
                + commandId + '\'');
      } else if ((bestActivation != null)
          && (DEBUG_VERBOSE)
          && ((DEBUG_VERBOSE_COMMAND_ID == null) || (DEBUG_VERBOSE_COMMAND_ID
              .equals(commandId)))) {
        Tracing
            .printTrace(TRACING_COMPONENT,
                "Resolved conflict detected.  The following activation won: "); //$NON-NLS-1$
        Tracing.printTrace(TRACING_COMPONENT, "    " + bestActivation); //$NON-NLS-1$
      }
    }

    // Return the current best.
    if (conflict) {
      if (previousLogs.add(commandId)) {
        final StringWriter sw = new StringWriter();
        final BufferedWriter buffer = new BufferedWriter(sw);
        try {
          buffer.write("Conflict for \'"); //$NON-NLS-1$
          buffer.write(commandId);
          buffer.write("\':"); //$NON-NLS-1$
          buffer.newLine();
          buffer.write(bestActivation.toString());
          buffer.newLine();
          buffer.write(currentActivation.toString());
          buffer.flush();
        } catch (IOException e) {
          //should never get this.
        }

        IStatus s = new Status(IStatus.WARNING,
            "org.eclipse.ui.workbench", //$NON-NLS-1$
            sw.toString());
        conflicts.add(s);
      }
      return null;
    }
    return bestActivation;
  }

  /**
   * Carries out the actual source change notification. It assumed that by the
   * time this method is called, <code>context</code> is up-to-date with the
   * current state of the application.
   *
   * @param sourcePriority
   *            A bit mask of all the source priorities that have changed.
   */
  protected final void sourceChanged(final int sourcePriority) {
    // If tracing, then track how long it takes to process the activations.
    long startTime = 0L;
    if (DEBUG_PERFORMANCE) {
      startTime = System.currentTimeMillis();
    }

    /*
     * In this first phase, we cycle through all of the activations that
     * could have potentially changed. Each such activation is added to a
     * set for future processing. We add it to a set so that we avoid
     * handling any individual activation more than once.
     */
    final Collection changedCommandIds = new HashSet(
        ACTIVATIONS_TO_RECOMPUTE_SIZE);
    for (int i = 1; i <= 32; i++) {
      if ((sourcePriority & (1 << i)) != 0) {
        final Map activationsByExpression = activationsByExpressionBySourcePriority[i];
        if (activationsByExpression != null) {
          final Iterator activationByExpressionItr = activationsByExpression
              .values().iterator();
          while (activationByExpressionItr.hasNext()) {
            final Collection activations = (Collection) activationByExpressionItr
                .next();
            final Iterator activationItr = activations.iterator();

            // Check the first activation to see if it has changed.
            if (activationItr.hasNext()) {
              IHandlerActivation activation = (IHandlerActivation) activationItr
                  .next();
              final boolean currentActive = evaluate(activation);
              activation.clearResult();
              final boolean newActive = evaluate(activation);
              if (newActive != currentActive) {
                changedCommandIds
                    .add(activation.getCommandId());

                // Then add every other activation as well.
                while (activationItr.hasNext()) {
                  activation = (IHandlerActivation) activationItr
                      .next();
                  activation.setResult(newActive);

                  changedCommandIds.add(activation
                      .getCommandId());
                }
              } else {
                while (activationItr.hasNext()) {
                  activation = (IHandlerActivation) activationItr
                      .next();
                  // if for some reason another activation
                  // doesn't match the new result, update and
                  // mark as changed. It's not as expensive
                  // as it looks :-)
                  if (newActive != evaluate(activation)) {
                    activation.setResult(newActive);
                    changedCommandIds.add(activation
                        .getCommandId());
                  }
                }
              }
            }
          }
        }
      }
    }

    MultiStatus conflicts = new MultiStatus("org.eclipse.ui.workbench", 0//$NON-NLS-1$
        "A handler conflict occurred.  This may disable some commands.", //$NON-NLS-1$
        null);
   
    /*
     * For every command identifier with a changed activation, we resolve
     * conflicts and trigger an update.
     */
    final Iterator changedCommandIdItr = changedCommandIds.iterator();
    while (changedCommandIdItr.hasNext()) {
      final String commandId = (String) changedCommandIdItr.next();
      final Object value = handlerActivationsByCommandId.get(commandId);
      if (value instanceof IHandlerActivation) {
        final IHandlerActivation activation = (IHandlerActivation) value;
        updateCommand(commandId, (evaluate(activation) ? activation
            : null));
      } else if (value instanceof SortedSet) {
        final IHandlerActivation activation = resolveConflicts(
            commandId, (SortedSet) value, conflicts);
        updateCommand(commandId, activation);
      } else {
        updateCommand(commandId, null);
      }
    }
    if (conflicts.getSeverity()!=IStatus.OK) {
      WorkbenchPlugin.log(conflicts);
    }

    // If tracing performance, then print the results.
    if (DEBUG_PERFORMANCE) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      final int size = changedCommandIds.size();
      if (size > 0) {
        Tracing.printTrace(TRACING_COMPONENT, size
            + " command ids changed in " + elapsedTime + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
      }
    }
  }

  /**
   * Updates the command with the given handler activation.
   *
   * @param commandId
   *            The identifier of the command which should be updated; must
   *            not be <code>null</code>.
   * @param activation
   *            The activation to use; may be <code>null</code> if the
   *            command should have a <code>null</code> handler.
   */
  private final void updateCommand(final String commandId,
      final IHandlerActivation activation) {
    final Command command = commandService.getCommand(commandId);
    if (activation == null) {
      command.setHandler(null);
    } else {
      command.setHandler(activation.getHandler());
      commandService.refreshElements(commandId, null);
    }
  }

  /**
   * <p>
   * Bug 95792. A mechanism by which the key binding architecture can force an
   * update of the handlers (based on the active shell) before trying to
   * execute a command. This mechanism is required for GTK+ only.
   * </p>
   * <p>
   * DO NOT CALL THIS METHOD.
   * </p>
   */
  final void updateShellKludge() {
    updateCurrentState();
    sourceChanged(ISources.ACTIVE_SHELL);
  }

  /**
   * Currently this is a an internal method to help locate a handler.
   * <p>
   * DO NOT CALL THIS METHOD.
   * </p>
   *
   * @param commandId
   *            the command id to check
   * @param context
   *            the context to use for activations
   * @since 3.3
   */
  public final IHandler findHandler(String commandId,
      IEvaluationContext context) {
    Object o = handlerActivationsByCommandId.get(commandId);
    if (o instanceof IHandlerActivation) {
      IHandlerActivation activation = (IHandlerActivation) o;
      try {
        if (eval(context, activation)) {
          return activation.getHandler();
        }
      } catch (CoreException e) {
        // the evalution failed
      }
    } else if (o instanceof SortedSet) {
      SortedSet activations = (SortedSet) o;
      IHandlerActivation lastActivation = null;
      IHandlerActivation currentActivation = null;
      Iterator i = activations.iterator();
      while (i.hasNext() && lastActivation==null) {
        IHandlerActivation activation = (IHandlerActivation) i.next();
        try {
          if (eval(context, activation)) {
            lastActivation = currentActivation;
            currentActivation = activation;
          }
        } catch (CoreException e) {
          // OK, this one is out of the running
        }
      }
      if (currentActivation != null) {
        if (lastActivation == null) {
          return currentActivation.getHandler();
        }
        if (lastActivation.getSourcePriority() != currentActivation
            .getSourcePriority()) {
          return lastActivation.getHandler();
        }
      }
    }
    return null;
  }

  /**
   * Evaluate the expression for the handler and bypass the result cache.
   * <p>
   * DO NOT CALL THIS METHOD.
   * </p>
   *
   * @param context
   * @param activation
   * @return <code>true</code> if the handler expression can evaluate to
   *         true.
   * @throws CoreException
   * @since 3.3
   */
  private boolean eval(IEvaluationContext context,
      IHandlerActivation activation) throws CoreException {
    Expression expression = activation.getExpression();
    if (expression == null) {
      return true;
    }
    return expression.evaluate(context) == EvaluationResult.TRUE;
  }

  /**
   * Normally the context returned from getCurrentState() still tracks the
   * application state. This method creates a copy and fills it in with the
   * variables that we know about. Currently it does not fill in the active
   * selection.
   * <p>
   * DO NOT CALL THIS METHOD. It is experimental in 3.3.
   * </p>
   *
   * @return an evaluation context with no parent.
   * @since 3.3
   */
  public IEvaluationContext getContextSnapshot() {
    return fillInContext(false);
  }
 
  /**
   * Normally the context returned from getCurrentState() still tracks the
   * application state. This method creates a copy and fills it in with all the
   * variables that we know about.
   * <p>
   * DO NOT CALL THIS METHOD. It is experimental in 3.3.
   * </p>
   *
   * @return an evaluation context with no parent.
   * @since 3.3
   */
  public IEvaluationContext getFullContextSnapshot() {
    return fillInContext(true);
  }
 
  private IEvaluationContext fillInContext(boolean fullContext) {
    IEvaluationContext tmpContext = getCurrentState();

    EvaluationContext context = null;
    if (fullContext) {
      context = new EvaluationContext(null, tmpContext.getDefaultVariable());
      copyVariable(context, tmpContext, ISources.ACTIVE_CURRENT_SELECTION_NAME);
      copyVariable(context, tmpContext, ISources.ACTIVE_FOCUS_CONTROL_ID_NAME);
      copyVariable(context, tmpContext, ISources.ACTIVE_FOCUS_CONTROL_NAME);
      copyVariable(context, tmpContext, ISources.ACTIVE_MENU_EDITOR_INPUT_NAME);
      copyVariable(context, tmpContext, ISources.ACTIVE_MENU_NAME);
      copyVariable(context, tmpContext, ISources.ACTIVE_MENU_SELECTION_NAME);
    } else {
      context = new EvaluationContext(null, Collections.EMPTY_LIST);
    }

    copyVariable(context, tmpContext, ISources.ACTIVE_ACTION_SETS_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_CONTEXT_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_EDITOR_ID_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_EDITOR_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_PART_ID_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_PART_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_SITE_NAME);
    copyVariable(context, tmpContext,
        ISources.ACTIVE_WORKBENCH_WINDOW_IS_COOLBAR_VISIBLE_NAME);
    copyVariable(context, tmpContext,
        ISources.ACTIVE_WORKBENCH_WINDOW_IS_PERSPECTIVEBAR_VISIBLE_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_WORKBENCH_WINDOW_NAME);
    copyVariable(context, tmpContext,
        ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME);
    copyVariable(context, tmpContext, ISources.ACTIVE_SHELL_NAME);

    return context;
  }
 
  private void copyVariable(IEvaluationContext context,
      IEvaluationContext tmpContext, String var) {
    Object o = tmpContext.getVariable(var);
    if (o != null) {
      context.addVariable(var, o);
    }
  }
}
TOP

Related Classes of org.eclipse.ui.internal.handlers.HandlerAuthority

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.