Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.InjectionException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.ui.PlatformUI;
Expand Down Expand Up @@ -334,7 +336,7 @@ private boolean loadHandler() {
// Load the handler.
try {
if (configurationElement != null) {
handler = (IHandler) configurationElement.createExecutableExtension(handlerAttributeName);
handler = adapt(configurationElement.createExecutableExtension(handlerAttributeName));
handler.addHandlerListener(getHandlerListener());
if (handler instanceof IObjectWithState) {
for (String id : getStateIds()) {
Expand All @@ -346,8 +348,9 @@ private boolean loadHandler() {
return true;
}

} catch (final ClassCastException e) {
final String message = "The proxied handler was the wrong class"; //$NON-NLS-1$
} catch (final InjectionException e) {
final String message = "The proxied handler '" //$NON-NLS-1$
+ getConfigurationElementAttribute() + "' could not be injected"; //$NON-NLS-1$
final IStatus status = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
WorkbenchPlugin.log(message, status);
configurationElement = null;
Expand All @@ -367,6 +370,18 @@ private boolean loadHandler() {
return true;
}

/**
* Wraps handler contributions that do not implement {@link IHandler} so that
* their {@code @Execute} and {@code @CanExecute} methods are dispatched through
* dependency injection.
*/
private static IHandler adapt(Object contribution) {
if (contribution instanceof IHandler handler) {
return handler;
}
return new PojoHandlerAdapter(contribution, PlatformUI.getWorkbench().getService(IEclipseContext.class));
}

private IHandlerListener getHandlerListener() {
if (handlerListener == null) {
handlerListener = handlerEvent -> fireHandlerChanged(new HandlerEvent(HandlerProxy.this,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
* Copyright (c) 2026 Lars Vogel 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:
* Lars Vogel <Lars.Vogel@vogella.com> - initial API and implementation
*******************************************************************************/

package org.eclipse.ui.internal.handlers;

import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.AbstractParameterValueConverter;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.ParameterType;
import org.eclipse.core.commands.ParameterValueConversionException;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.e4.core.commands.ExpressionContext;
import org.eclipse.e4.core.commands.internal.HandlerServiceImpl;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
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.core.di.annotations.CanExecute;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.internal.WorkbenchPlugin;

/**
* Adapts a handler contribution that does not implement
* {@link org.eclipse.core.commands.IHandler} to the legacy handler API by
* dispatching to its {@link Execute} and {@link CanExecute} methods through
* dependency injection.
*/
class PojoHandlerAdapter extends AbstractHandler {

private static final Object MISSING_EXECUTE = new Object();

private final Object handler;

/** The context the handler was injected from, may be <code>null</code>. */
private final IEclipseContext injectionContext;

/**
* Injects the given handler and prepares it for dispatch.
*
* @throws InjectionException if the handler cannot be injected
*/
PojoHandlerAdapter(Object handler, IEclipseContext injectionContext) {
this.handler = handler;
this.injectionContext = injectionContext;
if (injectionContext != null) {
ContextInjectionFactory.inject(handler, injectionContext);
}
}

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IEclipseContext executionContext = executionContextOf(event.getApplicationContext());
if (executionContext == null) {
throw new ExecutionException("No IEclipseContext available to execute " + handler.getClass().getName()); //$NON-NLS-1$
}
IEclipseContext staticContext = EclipseContextFactory.create();
try {
staticContext.set(HandlerServiceImpl.PARM_MAP, event.getParameters());
addParameters(event, staticContext);
staticContext.set(ExecutionEvent.class, event);
if (event.getTrigger() instanceof Event trigger) {
staticContext.set(Event.class, trigger);
}
Object result = ContextInjectionFactory.invoke(handler, Execute.class, executionContext, staticContext,
MISSING_EXECUTE);
if (result == MISSING_EXECUTE) {
throw new ExecutionException(handler.getClass().getName() + " handler is missing @Execute", //$NON-NLS-1$
new NotHandledException(handler.getClass().getName()));
}
return result;
} catch (InjectionException e) {
if (e.getCause() instanceof ExecutionException cause) {
throw cause;
}
throw new ExecutionException("Error executing " + handler.getClass().getName(), e); //$NON-NLS-1$
} finally {
staticContext.dispose();
}
}

@Override
public void setEnabled(Object evaluationContext) {
IEclipseContext executionContext = executionContextOf(evaluationContext);
if (executionContext == null) {
return;
}
IEclipseContext staticContext = EclipseContextFactory.create();
try {
Object result = ContextInjectionFactory.invoke(handler, CanExecute.class, executionContext, staticContext,
Boolean.TRUE);
// a @CanExecute that does not return boolean leaves enablement untouched
if (result instanceof Boolean enabled) {
setBaseEnabled(enabled.booleanValue());
}
} catch (InjectionException e) {
WorkbenchPlugin.log("Error while evaluating @CanExecute of " + handler.getClass().getName(), e); //$NON-NLS-1$
setBaseEnabled(false);
} finally {
staticContext.dispose();
}
}

private static void addParameters(ExecutionEvent event, IEclipseContext staticContext) {
Command command = event.getCommand();
Map<?, ?> parameters = event.getParameters();
if (command == null || parameters == null) {
return;
}
for (Entry<?, ?> parameter : parameters.entrySet()) {
if (parameter.getKey() instanceof String parameterId) {
staticContext.set(parameterId, convertParameterValue(command, parameterId, parameter.getValue()));
}
}
ParameterizedCommand parameterizedCommand = ParameterizedCommand.generateCommand(command, parameters);
if (parameterizedCommand != null) {
staticContext.set(ParameterizedCommand.class, parameterizedCommand);
}
}

private static Object convertParameterValue(Command command, String parameterId, Object value) {
if (value instanceof String stringValue) {
try {
ParameterType parameterType = command.getParameterType(parameterId);
if (parameterType != null) {
AbstractParameterValueConverter converter = parameterType.getValueConverter();
if (converter != null) {
return converter.convertToObject(stringValue);
}
}
} catch (NotDefinedException | ParameterValueConversionException e) {
return stringValue;
}
}
return value;
}

/**
* Unwraps the {@link IEclipseContext} the given evaluation object was created
* from, falling back to the active leaf of the injection context.
*/
private IEclipseContext executionContextOf(Object evaluationObject) {
if (evaluationObject instanceof IEclipseContext context) {
return context;
}
if (evaluationObject instanceof ExpressionContext context) {
return context.eclipseContext;
}
if (evaluationObject instanceof IEvaluationContext context) {
return executionContextOf(context.getParent());
}
return injectionContext == null ? null : injectionContext.getActiveLeaf();
}

@Override
public void dispose() {
if (injectionContext != null) {
try {
ContextInjectionFactory.uninject(handler, injectionContext);
} catch (InjectionException e) {
WorkbenchPlugin.log("Error while uninjecting " + handler.getClass().getName(), e); //$NON-NLS-1$
}
}
super.dispose();
}

@Override
public String toString() {
return handler.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
ToggleStateTest.class,
RadioStateTest.class,
WorkbenchStateTest.class,
E4CommandImageTest.class
E4CommandImageTest.class,
PojoHandlerTest.class
})
public final class CommandsTestSuite {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2026 Lars Vogel 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:
* Lars Vogel <Lars.Vogel@vogella.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.tests.commands;

import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.CanExecute;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.core.di.annotations.Optional;

import jakarta.inject.Inject;
import jakarta.inject.Named;

/**
* A handler contributed to <code>org.eclipse.ui.handlers</code> that implements
* neither <code>IHandler</code> nor any other platform interface.
*/
public class PojoHandler {

public static final String PARAMETER_ID = "org.eclipse.ui.tests.commands.pojoHandler.value"; //$NON-NLS-1$

public static boolean executed;

public static String parameterValue;

public static boolean canExecute = true;

public static IEclipseContext injectedContext;

@Inject
void setContext(IEclipseContext context) {
injectedContext = context;
}

@CanExecute
public boolean canExecute() {
return canExecute;
}

@Execute
public Object execute(@Optional @Named(PARAMETER_ID) String value) {
executed = true;
parameterValue = value;
return "executed"; //$NON-NLS-1$
}
}
Loading
Loading