Skip to content
Merged
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,191 @@
/*******************************************************************************
* 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.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;
}

/**
* Resolves the context to run the handler in: the active leaf below the
* {@link IEclipseContext} the given evaluation object was created from, as the
* E4 handler service does, since legacy enablement updates pass the window
* state rather than the active part.
*/
private IEclipseContext executionContextOf(Object evaluationObject) {
IEclipseContext context = null;
if (evaluationObject instanceof IEclipseContext eclipseContext) {
context = eclipseContext;
} else if (evaluationObject instanceof IEvaluationContext evaluationContext
&& evaluationContext.getVariable(IEclipseContext.class.getName()) instanceof IEclipseContext eclipseContext) {
context = eclipseContext;
} else {
context = injectionContext;
}
return context == null ? null : context.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
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2007, 2015 IBM Corporation and others.
* Copyright (c) 2007, 2026 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
Expand All @@ -14,12 +14,13 @@

package org.eclipse.ui.internal.handlers;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;

import jakarta.inject.Named;

/**
* Exit the workbench. Normal invocation calls {@link IWorkbench#close()}, which
* typically doesn't prompt the user before exiting.
Expand All @@ -30,19 +31,16 @@
*
* @since 3.4
*/
public class QuitHandler extends AbstractHandler {
public class QuitHandler {
private static final String COMMAND_PARAMETER_ID_MAY_PROMPT = "mayPrompt"; //$NON-NLS-1$
private static final String TRUE = "true"; //$NON-NLS-1$

@Override
public Object execute(ExecutionEvent event) {
IEvaluationContext context = (IEvaluationContext) event.getApplicationContext();
IWorkbench workbench = (IWorkbench) context.getVariable(IWorkbench.class.getName());
if (TRUE.equals(event.getParameter(COMMAND_PARAMETER_ID_MAY_PROMPT))) {
@Execute
public void execute(IWorkbench workbench, @Optional @Named(COMMAND_PARAMETER_ID_MAY_PROMPT) String mayPrompt) {
if (TRUE.equals(mayPrompt)) {
workbench.getDisplay().close();
} else {
workbench.close();
}
return null;
}
}
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,75 @@
/*******************************************************************************
* 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 org.eclipse.e4.ui.model.application.ui.basic.MPart;

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;

public static MPart canExecutePart;

public static MPart executePart;

public static IEclipseContext executeContext;

static void reset() {
executed = false;
parameterValue = null;
canExecute = true;
canExecutePart = null;
executePart = null;
executeContext = null;
}

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

@CanExecute
public boolean canExecute(@Optional MPart part) {
canExecutePart = part;
return canExecute;
}

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