blob: 38359a88a7eb76e3302c1371c78cbd43887c3500 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
* Daniel Kruegler <daniel.kruegler@gmail.com> - Bug 487418, 494840
* Friederike Schertel <friederike@schertel.org> - Bug 478336
******************************************************************************/
package org.eclipse.ui.internal.handlers;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.expressions.AndExpression;
import org.eclipse.core.expressions.ElementHandler;
import org.eclipse.core.expressions.EvaluationContext;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.ExpressionConverter;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.e4.core.commands.ECommandService;
import org.eclipse.e4.core.commands.EHandlerService;
import org.eclipse.e4.core.commands.ExpressionContext;
import org.eclipse.e4.core.commands.internal.HandlerServiceHandler;
import org.eclipse.e4.core.commands.internal.HandlerServiceImpl;
import org.eclipse.e4.core.commands.internal.ICommandHelpService;
import org.eclipse.e4.core.contexts.ContextFunction;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.InjectionException;
import org.eclipse.e4.ui.internal.workbench.Activator;
import org.eclipse.e4.ui.internal.workbench.Policy;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.ISourceProvider;
import org.eclipse.ui.ISources;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.expressions.WorkbenchWindowExpression;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.services.EvaluationService;
import org.eclipse.ui.services.IEvaluationService;
import org.eclipse.ui.services.ISourceProviderService;
/**
* @since 3.5
*
*/
public class LegacyHandlerService implements IHandlerService {
private static final String[] SELECTION_VARIABLES = { ISources.ACTIVE_CURRENT_SELECTION_NAME,
ISources.ACTIVE_FOCUS_CONTROL_ID_NAME, ISources.ACTIVE_FOCUS_CONTROL_NAME,
ISources.ACTIVE_MENU_EDITOR_INPUT_NAME, ISources.ACTIVE_MENU_NAME, ISources.ACTIVE_MENU_SELECTION_NAME };
public static final String LEGACY_H_ID = "legacy::handler::"; //$NON-NLS-1$
static class HandlerSelectionFunction extends ContextFunction {
private final String commandId;
public HandlerSelectionFunction(String commandId) {
this.commandId = commandId;
}
@Override
public Object compute(IEclipseContext context, String contextKey) {
HashSet<HandlerActivation> activationSet = new LinkedHashSet<>();
IEclipseContext current = context;
while (current != null) {
@SuppressWarnings("unchecked")
List<HandlerActivation> handlerActivations = (List<HandlerActivation>) current
.getLocal(LEGACY_H_ID + commandId);
if (handlerActivations != null) {
activationSet.addAll(handlerActivations);
}
current = current.getParent();
}
if (activationSet.isEmpty()) {
return null;
}
HandlerActivation bestActivation = null;
ExpressionContext legacyEvalContext = new ExpressionContext(context);
HandlerActivation conflictBest = null;
HandlerActivation conflictOther = null;
for (HandlerActivation handlerActivation : activationSet) {
if (!handlerActivation.participating)
continue;
if (handlerActivation.evaluate(legacyEvalContext)) {
if (bestActivation == null) {
bestActivation = handlerActivation;
} else {
int comparison = bestActivation.compareTo(handlerActivation);
if (comparison < 0) {
bestActivation = handlerActivation;
} else if (comparison == 0) {
conflictBest = bestActivation;
conflictOther = handlerActivation;
}
}
}
}
if (bestActivation != null) {
if (bestActivation == conflictBest) {
String fullMessage = "Conflicting handlers: {" //$NON-NLS-1$
+ conflictBest + "} vs {" //$NON-NLS-1$
+ conflictOther + "} in: " + context; //$NON-NLS-1$
String shortMessage = "Conflicting handlers for " + commandId + ": {" //$NON-NLS-1$ //$NON-NLS-2$
+ conflictBest.getHandler() + "} vs {" //$NON-NLS-1$
+ conflictOther.getHandler() + "} in: " + context; //$NON-NLS-1$
WorkbenchPlugin.log(shortMessage, new IllegalStateException(fullMessage));
}
return bestActivation.proxy;
}
return null;
}
}
private static IHandlerActivation systemHandlerActivation;
public static IHandlerActivation registerLegacyHandler(final IEclipseContext context, String id, final String cmdId,
IHandler handler, Expression activeWhen) {
return registerLegacyHandler(context, id, cmdId, handler, activeWhen, null, null);
}
private static IHandlerActivation registerLegacyHandler(final IEclipseContext context, String id,
final String cmdId, IHandler handler, Expression activeWhen, String helpContextId,
Collection<HandlerActivation> handlerActivations) {
ECommandService cs = context.get(ECommandService.class);
Command command = cs.getCommand(cmdId);
E4HandlerProxy handlerProxy = new E4HandlerProxy(command, handler);
if (helpContextId != null) {
setHelpContextId(handler, helpContextId, context);
}
HandlerActivation activation = new HandlerActivation(context, cmdId, handler, handlerProxy, activeWhen);
addHandlerActivation(activation);
EHandlerService hs = context.get(EHandlerService.class);
hs.activateHandler(cmdId, new HandlerSelectionFunction(cmdId));
if (handlerActivations != null) {
handlerActivations.add(activation);
}
return activation;
}
static void addHandlerActivation(HandlerActivation eActivation) {
@SuppressWarnings("unchecked")
List<HandlerActivation> handlerActivations = (List<HandlerActivation>) eActivation.context
.getLocal(LEGACY_H_ID + eActivation.getCommandId());
if (handlerActivations == null) {
handlerActivations = new ArrayList<>();
} else {
if (handlerActivations.contains(eActivation)) {
return;
}
handlerActivations = new ArrayList<>(handlerActivations);
}
handlerActivations.add(eActivation);
// setting this so that we trigger invalidations
eActivation.context.set(LEGACY_H_ID + eActivation.getCommandId(), handlerActivations);
}
static void removeHandlerActivation(HandlerActivation eActivation) {
@SuppressWarnings("unchecked")
List<HandlerActivation> handlerActivations = (List<HandlerActivation>) eActivation.context
.getLocal(LEGACY_H_ID + eActivation.getCommandId());
if (handlerActivations == null) {
handlerActivations = new ArrayList<>();
} else {
handlerActivations = new ArrayList<>(handlerActivations);
}
handlerActivations.remove(eActivation);
// setting this so that we trigger invalidations
eActivation.context.set(LEGACY_H_ID + eActivation.getCommandId(), handlerActivations);
}
private IEclipseContext eclipseContext;
private IEvaluationContext evalContext;
private Expression defaultExpression = null;
/**
* The handler activations that have come from the registry. This is used to
* flush the activations when the registry is re-read. This value is never
* <code>null</code>
*/
private final Collection<HandlerActivation> handlerActivations = new ArrayList<>();
public LegacyHandlerService(IEclipseContext context) {
eclipseContext = context;
evalContext = new ExpressionContext(eclipseContext);
IWorkbenchWindow window = eclipseContext.get(IWorkbenchWindow.class);
if (window != null) {
defaultExpression = new WorkbenchWindowExpression(window);
}
}
public LegacyHandlerService(IEclipseContext context, Expression defaultExpression) {
eclipseContext = context;
evalContext = new ExpressionContext(eclipseContext);
this.defaultExpression = defaultExpression;
}
@Override
public void addSourceProvider(ISourceProvider provider) {
}
@Override
public void removeSourceProvider(ISourceProvider provider) {
}
/**
* Deactivates all of the activations read from the registry, and then clears
* the collection. This should be called before every read.
*/
private void clearActivations() {
deactivateHandlers(handlerActivations);
for (IHandlerActivation activation : handlerActivations) {
final IHandler handler = activation.getHandler();
if (handler != null) {
SafeRunner.run(new ISafeRunnable() {
@Override
public void run() throws Exception {
handler.dispose();
}
@Override
public void handleException(Throwable exception) {
WorkbenchPlugin.log("Failed to dispose handler for " //$NON-NLS-1$
+ activation.getCommandId(), exception);
}
});
}
}
handlerActivations.clear();
}
@Override
public void dispose() {
clearActivations();
defaultExpression = null;
eclipseContext = null;
evalContext = null;
}
@Override
public IHandlerActivation activateHandler(IHandlerActivation activation) {
if (activation == systemHandlerActivation) {
return activation;
}
HandlerActivation handlerActivation = (HandlerActivation) activation;
handlerActivation.participating = true;
addHandlerActivation(handlerActivation);
return activation;
}
@Override
public IHandlerActivation activateHandler(String commandId, IHandler handler) {
return activateHandler(commandId, handler, null, false);
}
@Override
public IHandlerActivation activateHandler(String commandId, IHandler handler, Expression expression) {
return activateHandler(commandId, handler, expression, false);
}
@Override
public IHandlerActivation activateHandler(String commandId, IHandler handler, Expression expression,
boolean global) {
if (global || defaultExpression == null) {
return registerLegacyHandler(eclipseContext, commandId, commandId, handler, expression);
}
Expression e;
if (expression != null) {
AndExpression andExpr = new AndExpression();
andExpr.add(expression);
andExpr.add(defaultExpression);
e = andExpr;
} else {
e = defaultExpression;
}
return registerLegacyHandler(eclipseContext, commandId, commandId, handler, e);
}
@Override
public IHandlerActivation activateHandler(String commandId, IHandler handler, Expression expression,
int sourcePriorities) {
return activateHandler(commandId, handler, expression, false);
}
@Override
public ExecutionEvent createExecutionEvent(Command command, Event event) {
EvaluationContext legacy = new EvaluationContext(evalContext, evalContext.getDefaultVariable());
return new ExecutionEvent(command, Collections.EMPTY_MAP, event, legacy);
}
@Override
public ExecutionEvent createExecutionEvent(ParameterizedCommand command, Event event) {
EvaluationContext legacy = new EvaluationContext(evalContext, evalContext.getDefaultVariable());
return new ExecutionEvent(command.getCommand(), command.getParameterMap(), event, legacy);
}
@Override
public void deactivateHandler(IHandlerActivation activation) {
// null is not allowed, but some people put it anyway :( see bug 326406
if (activation != null && activation != systemHandlerActivation) {
HandlerActivation eActivation = (HandlerActivation) activation;
eActivation.participating = false;
removeHandlerActivation(eActivation);
}
}
@Override
public void deactivateHandlers(Collection activations) {
// set all activations to not be participating first so that they ignore
// the upcoming context change events
for (Object activation : activations) {
((HandlerActivation) activation).participating = false;
}
for (Object element : activations) {
deactivateHandler((IHandlerActivation) element);
}
}
@Override
public Object executeCommand(String commandId, Event event)
throws ExecutionException, NotDefinedException, NotEnabledException, NotHandledException {
ECommandService cs = eclipseContext.get(ECommandService.class);
final Command command = cs.getCommand(commandId);
return executeCommand(ParameterizedCommand.generateCommand(command, null), event);
}
@Override
public Object executeCommand(ParameterizedCommand command, Event event)
throws ExecutionException, NotDefinedException, NotEnabledException, NotHandledException {
EHandlerService hs = eclipseContext.get(EHandlerService.class);
if (hs == null) {
// If the log shows an IEclipseContext named "Anonymous" this is an
// indication that the context is disposed before we try to execute
// the command
WorkbenchPlugin.log("IEclipseContext is " + eclipseContext); //$NON-NLS-1$
WorkbenchPlugin.log("EHandlerService is null", //$NON-NLS-1$
new IllegalStateException("EHandlerService must not be null")); //$NON-NLS-1$
throw new ExecutionException("No handler service available"); //$NON-NLS-1$
}
IEclipseContext staticContext = EclipseContextFactory.create();
if (event != null) {
staticContext.set(Event.class, event);
}
try {
final Object rc = hs.executeHandler(command, staticContext);
final Object obj = staticContext.get(HandlerServiceImpl.HANDLER_EXCEPTION);
if (obj instanceof ExecutionException) {
throw (ExecutionException) obj;
} else if (obj instanceof NotDefinedException) {
throw (NotDefinedException) obj;
} else if (obj instanceof NotEnabledException) {
throw (NotEnabledException) obj;
} else if (obj instanceof NotHandledException) {
throw (NotHandledException) obj;
} else if (obj instanceof Exception) {
WorkbenchPlugin.log((Exception) obj);
}
return rc;
} catch (InjectionException e) {
rethrow(e);
throw e;
} finally {
staticContext.dispose();
}
}
@Override
public Object executeCommandInContext(ParameterizedCommand command, Event event, IEvaluationContext context)
throws ExecutionException, NotDefinedException, NotEnabledException, NotHandledException {
IHandler handler = command.getCommand().getHandler();
boolean enabled = handler.isEnabled();
IEclipseContext staticContext = null;
Object defaultVar = null;
if (context instanceof ExpressionContext) {
// create a child context so that the primary context doesn't get
// populated by parameters by the EHS
staticContext = ((ExpressionContext) context).eclipseContext.createChild();
} else {
staticContext = eclipseContext.getActiveLeaf().createChild("snapshotContext"); //$NON-NLS-1$
if (event != null) {
staticContext.set(Event.class, event);
}
staticContext.set(IEvaluationContext.class, context);
defaultVar = context.getDefaultVariable();
if (defaultVar != null && defaultVar != IEvaluationContext.UNDEFINED_VARIABLE) {
staticContext.set(EvaluationService.DEFAULT_VAR, defaultVar);
}
}
IEclipseContext lookupContext = staticContext;
// the IEvaluationContext snapshot is not part of our runtime
// IEclipseContext hierarchy. In order to work. we need the variables
// from the snapshot available in the static context.
populateSnapshot(context, staticContext);
EHandlerService hs = lookupContext.get(EHandlerService.class);
try {
final Object rc = hs.executeHandler(command, staticContext);
final Object obj = staticContext.get(HandlerServiceImpl.HANDLER_EXCEPTION);
if (obj instanceof ExecutionException) {
throw (ExecutionException) obj;
} else if (obj instanceof NotDefinedException) {
throw (NotDefinedException) obj;
} else if (obj instanceof NotEnabledException) {
throw (NotEnabledException) obj;
} else if (obj instanceof NotHandledException) {
throw (NotHandledException) obj;
} else if (obj instanceof Exception) {
WorkbenchPlugin.log((Exception) obj);
}
return rc;
} catch (InjectionException e) {
rethrow(e);
throw e;
} finally {
if (handler.isEnabled() != enabled && handler instanceof HandlerServiceHandler) {
((HandlerServiceHandler) handler).overrideEnabled(enabled);
}
staticContext.dispose();
}
}
/**
* @param context
* @param staticContext
*/
private void populateSnapshot(IEvaluationContext context, IEclipseContext staticContext) {
IEvaluationContext ctxPtr = context;
while (ctxPtr != null && !(ctxPtr instanceof ExpressionContext)) {
Map<?, ?> vars = getVariables(ctxPtr);
if (vars != null) {
Iterator<?> i = vars.entrySet().iterator();
while (i.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
if (staticContext.getLocal(entry.getKey().toString()) == null) {
staticContext.set(entry.getKey().toString(), entry.getValue());
}
}
}
ctxPtr = ctxPtr.getParent();
}
}
private Map<?, ?> getVariables(IEvaluationContext ctx) {
Field vars = getContextVariablesField();
if (vars != null) {
try {
return (Map<?, ?>) vars.get(ctx);
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
return null;
}
private static Field contextFVariables = null;
private static Field getContextVariablesField() {
if (contextFVariables == null) {
try {
contextFVariables = EvaluationContext.class.getField("fVariables"); //$NON-NLS-1$
contextFVariables.setAccessible(true);
} catch (SecurityException | NoSuchFieldException e) {
}
}
return contextFVariables;
}
/**
* Checks the cause of the provided exception and rethrows the cause instead if
* it was one of the expected exception types.
*/
private void rethrow(InjectionException e)
throws ExecutionException, NotDefinedException, NotEnabledException, NotHandledException {
Throwable cause = e.getCause();
if (cause instanceof ExecutionException) {
throw (ExecutionException) cause;
} else if (cause instanceof NotDefinedException) {
throw (NotDefinedException) cause;
} else if (cause instanceof NotEnabledException) {
throw (NotEnabledException) cause;
} else if (cause instanceof NotHandledException) {
throw (NotHandledException) cause;
}
}
@Override
public IEvaluationContext createContextSnapshot(boolean includeSelection) {
IEvaluationContext tmpContext = getCurrentState();
IEvaluationContext context = new EvaluationContext(null, IEvaluationContext.UNDEFINED_VARIABLE);
if (includeSelection) {
for (String variable : SELECTION_VARIABLES) {
copyVariable(context, tmpContext, variable);
}
}
ISourceProviderService sp = eclipseContext.get(ISourceProviderService.class);
for (ISourceProvider provider : sp.getSourceProviders()) {
String[] names = provider.getProvidedSourceNames();
for (String name : names) {
if (!isSelectionVariable(name)) {
copyVariable(context, tmpContext, name);
}
}
}
return context;
}
private boolean isSelectionVariable(String name) {
for (String variable : SELECTION_VARIABLES) {
if (variable.equals(name)) {
return true;
}
}
return false;
}
private void copyVariable(IEvaluationContext context, IEvaluationContext tmpContext, String var) {
Object o = tmpContext.getVariable(var);
if (o != null) {
context.addVariable(var, o);
}
}
@Override
public IEvaluationContext getCurrentState() {
return new EvaluationContext(evalContext, evalContext.getDefaultVariable());
}
@Override
public void readRegistry() {
clearActivations();
readDefaultHandlers();
readHandlers();
}
private void readHandlers() {
IExtensionRegistry registry = eclipseContext.get(IExtensionRegistry.class);
IExtensionPoint extPoint = registry.getExtensionPoint(IWorkbenchRegistryConstants.EXTENSION_HANDLERS);
IConfigurationElement[] elements = extPoint.getConfigurationElements();
for (IConfigurationElement configElement : elements) {
String commandId = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_COMMAND_ID);
if (commandId == null || commandId.isEmpty()) {
continue;
}
String defaultHandler = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_CLASS);
if ((defaultHandler == null)
&& (configElement.getChildren(IWorkbenchRegistryConstants.TAG_CLASS).length == 0)) {
continue;
}
Expression activeWhen = null;
final IConfigurationElement[] awChildren = configElement
.getChildren(IWorkbenchRegistryConstants.TAG_ACTIVE_WHEN);
if (awChildren.length > 0) {
final IConfigurationElement[] subChildren = awChildren[0].getChildren();
if (subChildren.length != 1) {
if (Policy.DEBUG_CMDS) {
Activator.trace(Policy.DEBUG_CMDS_FLAG, "Incorrect activeWhen element " + commandId, null); //$NON-NLS-1$
}
continue;
}
final ElementHandler elementHandler = ElementHandler.getDefault();
final ExpressionConverter converter = ExpressionConverter.getDefault();
try {
activeWhen = elementHandler.create(converter, subChildren[0]);
} catch (CoreException e) {
if (Policy.DEBUG_CMDS) {
Activator.trace(Policy.DEBUG_CMDS_FLAG, "Incorrect activeWhen element " + commandId, e); //$NON-NLS-1$
}
}
}
Expression enabledWhen = null;
final IConfigurationElement[] ewChildren = configElement
.getChildren(IWorkbenchRegistryConstants.TAG_ENABLED_WHEN);
if (ewChildren.length > 0) {
final IConfigurationElement[] subChildren = ewChildren[0].getChildren();
if (subChildren.length != 1) {
if (Policy.DEBUG_CMDS) {
Activator.trace(Policy.DEBUG_CMDS_FLAG, "Incorrect enableWhen element " + commandId, null); //$NON-NLS-1$
}
continue;
}
final ElementHandler elementHandler = ElementHandler.getDefault();
final ExpressionConverter converter = ExpressionConverter.getDefault();
try {
enabledWhen = elementHandler.create(converter, subChildren[0]);
} catch (CoreException e) {
if (Policy.DEBUG_CMDS) {
Activator.trace(Policy.DEBUG_CMDS_FLAG, "Incorrect enableWhen element " + commandId, e); //$NON-NLS-1$
}
}
}
registerLegacyHandler(eclipseContext, commandId, commandId,
new org.eclipse.ui.internal.handlers.HandlerProxy(commandId, configElement,
IWorkbenchRegistryConstants.ATT_CLASS, enabledWhen,
eclipseContext.get(IEvaluationService.class)),
activeWhen, configElement.getAttribute(IWorkbenchRegistryConstants.ATT_HELP_CONTEXT_ID),
handlerActivations);
}
}
private void readDefaultHandlers() {
IExtensionRegistry registry = eclipseContext.get(IExtensionRegistry.class);
IExtensionPoint extPoint = registry.getExtensionPoint(IWorkbenchRegistryConstants.EXTENSION_COMMANDS);
IConfigurationElement[] elements = extPoint.getConfigurationElements();
for (IConfigurationElement configElement : elements) {
String id = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_ID);
if (id == null || id.isEmpty()) {
continue;
}
String defaultHandler = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_DEFAULT_HANDLER);
if ((defaultHandler == null)
&& (configElement.getChildren(IWorkbenchRegistryConstants.TAG_DEFAULT_HANDLER).length == 0)) {
continue;
}
registerLegacyHandler(eclipseContext, id, id, new org.eclipse.ui.internal.handlers.HandlerProxy(id,
configElement, IWorkbenchRegistryConstants.ATT_DEFAULT_HANDLER), null, null, handlerActivations);
}
}
@Override
public void setHelpContextId(IHandler handler, String helpContextId) {
setHelpContextId(handler, helpContextId, eclipseContext);
}
private static void setHelpContextId(IHandler handler, String helpContextId, IEclipseContext eclipseContext) {
ICommandHelpService commandHelpService = eclipseContext.get(ICommandHelpService.class);
commandHelpService.setHelpContextId(handler, helpContextId);
}
}