/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG 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:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.e4.launcher.part;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.commands.AbstractHandlerWithState;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.HandlerEvent;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.IHandler2;
import org.eclipse.core.commands.IHandlerListener;
import org.eclipse.core.commands.IStateListener;
import org.eclipse.core.commands.State;
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.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.commands.IElementUpdater;
import org.eclipse.ui.handlers.RadioState;
import org.eclipse.ui.handlers.RegistryToggleState;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.menus.UIElement;
import org.eclipse.ui.services.IEvaluationReference;
import org.eclipse.ui.services.IEvaluationService;
/**
* <p>
* A proxy for a handler that has been defined in XML. This delays the class loading until the handler is really asked for information (besides the priority or
* the command identifier). Asking a proxy for anything but the attributes defined publicly in this class will cause the proxy to instantiate the proxied
* handler.
* </p>
*
* @since 6.1
*/
public final class HandlerProxy extends AbstractHandlerWithState implements IElementUpdater {
private static Map CEToProxyMap = new HashMap();
/**
*
*/
private static final String PROP_ENABLED = "enabled"; //$NON-NLS-1$
/**
* The configuration element from which the handler can be created. This value will exist until the element is converted into a real class -- at which point
* this value will be set to <code>null</code>.
*/
private IConfigurationElement configurationElement;
/**
* The <code>enabledWhen</code> expression for the handler. Only if this expression evaluates to <code>true</code> (or the value is <code>null</code>)
* should we consult the handler.
*/
private final Expression enabledWhenExpression;
/**
* The real handler. This value is <code>null</code> until the proxy is forced to load the real handler. At this point, the configuration element is
* converted, nulled out, and this handler gains a reference.
*/
private IHandler handler = null;
/**
* The name of the configuration element attribute which contains the information necessary to instantiate the real handler.
*/
private final String handlerAttributeName;
private IHandlerListener handlerListener;
/**
* The evaluation service to use when evaluating <code>enabledWhenExpression</code>. This value may be <code>null</code> only if the
* <code>enabledWhenExpression</code> is <code>null</code>.
*/
private final IEvaluationService evaluationService;
private IPropertyChangeListener enablementListener;
private IEvaluationReference enablementRef;
private boolean proxyEnabled;
private final String commandId;
//
// state to support checked or radio commands.
private State checkedState;
private State radioState;
// Exception that occurs while loading the proxied handler class
private Exception loadException;
/**
* Constructs a new instance of <code>HandlerProxy</code> with all the information it needs to try to avoid loading until it is needed.
*
* @param commandId
* the id for this handler
* @param configurationElement
* The configuration element from which the real class can be loaded at run-time; must not be <code>null</code>.
* @param handlerAttributeName
* The name of the attibute or element containing the handler executable extension; must not be <code>null</code>.
*/
public HandlerProxy(final String commandId, final IConfigurationElement configurationElement, final String handlerAttributeName) {
this(commandId, configurationElement, handlerAttributeName, null, null);
}
/**
* Constructs a new instance of <code>HandlerProxy</code> with all the information it needs to try to avoid loading until it is needed.
*
* @param commandId
* the id for this handler
* @param configurationElement
* The configuration element from which the real class can be loaded at run-time; must not be <code>null</code>.
* @param handlerAttributeName
* The name of the attribute or element containing the handler executable extension; must not be <code>null</code>.
* @param enabledWhenExpression
* The name of the element containing the enabledWhen expression. This should be a child of the <code>configurationElement</code>. If this value
* is <code>null</code>, then there is no enablement expression (i.e., enablement will be delegated to the handler when possible).
* @param evaluationService
* The evaluation service to manage enabledWhen expressions trying to evaluate the <code>enabledWhenExpression</code>. This value may be
* <code>null</code> only if the <code>enabledWhenExpression</code> is <code>null</code>.
*/
public HandlerProxy(final String commandId, final IConfigurationElement configurationElement, final String handlerAttributeName,
final Expression enabledWhenExpression, final IEvaluationService evaluationService) {
if (configurationElement == null) {
throw new NullPointerException("The configuration element backing a handler proxy cannot be null"); //$NON-NLS-1$
}
if (handlerAttributeName == null) {
throw new NullPointerException("The attribute containing the handler class must be known"); //$NON-NLS-1$
}
if ((enabledWhenExpression != null) && (evaluationService == null)) {
throw new NullPointerException("We must have a handler service and evaluation service to support the enabledWhen expression"); //$NON-NLS-1$
}
this.commandId = commandId;
this.configurationElement = configurationElement;
this.handlerAttributeName = handlerAttributeName;
this.enabledWhenExpression = enabledWhenExpression;
this.evaluationService = evaluationService;
if (enabledWhenExpression != null) {
setProxyEnabled(false);
registerEnablement();
} else {
setProxyEnabled(true);
}
CEToProxyMap.put(configurationElement, this);
}
public static void updateStaleCEs(final IConfigurationElement[] replacements) {
for (final IConfigurationElement replacement : replacements) {
final HandlerProxy proxy = (HandlerProxy) CEToProxyMap.get(replacement);
if (proxy != null) {
proxy.configurationElement = replacement;
}
}
}
/**
*
*/
private void registerEnablement() {
enablementRef = evaluationService.addEvaluationListener(enabledWhenExpression, getEnablementListener(), PROP_ENABLED);
}
@Override
public void setEnabled(final Object evaluationContext) {
if (!(evaluationContext instanceof IEvaluationContext)) {
return;
}
final IEvaluationContext context = (IEvaluationContext) evaluationContext;
if (enabledWhenExpression != null) {
try {
setProxyEnabled(enabledWhenExpression.evaluate(context) == EvaluationResult.TRUE);
} catch (final CoreException e) {
// TODO should we log this exception, or just treat it as
// a failure
}
}
if (isOkToLoad() && loadHandler()) {
if (handler instanceof IHandler2) {
((IHandler2) handler).setEnabled(evaluationContext);
}
}
}
void setProxyEnabled(final boolean enabled) {
proxyEnabled = enabled;
}
boolean getProxyEnabled() {
return proxyEnabled;
}
private IPropertyChangeListener getEnablementListener() {
if (enablementListener == null) {
enablementListener = new IPropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent event) {
if (event.getProperty() == PROP_ENABLED) {
setProxyEnabled(event.getNewValue() == null ? false : ((Boolean) event.getNewValue()).booleanValue());
fireHandlerChanged(new HandlerEvent(HandlerProxy.this, true, false));
}
}
};
}
return enablementListener;
}
/**
* Passes the dipose on to the proxied handler, if it has been loaded.
*/
@Override
public final void dispose() {
if (handler != null) {
if (handlerListener != null) {
handler.removeHandlerListener(handlerListener);
handlerListener = null;
}
handler.dispose();
handler = null;
}
if (enablementListener != null) {
evaluationService.removeEvaluationListener(enablementRef);
enablementRef = null;
enablementListener = null;
}
}
public final Object execute(final ExecutionEvent event) throws ExecutionException {
if (loadHandler()) {
if (!isEnabled()) {
MessageDialog.openInformation(Util.getShellToParentOn(), WorkbenchMessages.Information, WorkbenchMessages.PluginAction_disabledMessage);
return null;
}
return handler.execute(event);
}
if (loadException != null) {
throw new ExecutionException("Exception occured when loading the handler", loadException); //$NON-NLS-1$
}
return null;
}
@Override
public final boolean isEnabled() {
if (enabledWhenExpression != null) {
// proxyEnabled reflects the enabledWhen clause
if (!getProxyEnabled()) {
return false;
}
if (isOkToLoad() && loadHandler()) {
return handler.isEnabled();
}
return true;
}
/*
* There is no enabled when expression, so we just need to consult the handler.
*/
if (isOkToLoad() && loadHandler()) {
return handler.isEnabled();
}
return true;
}
@Override
public final boolean isHandled() {
if (configurationElement != null && handler == null) {
return true;
}
if (isOkToLoad() && loadHandler()) {
return handler.isHandled();
}
return false;
}
/**
* Loads the handler, if possible. If the handler is loaded, then the member variables are updated accordingly.
*
* @return <code>true</code> if the handler is now non-null; <code>false</code> otherwise.
*/
private final boolean loadHandler() {
if (handler == null) {
// Load the handler.
try {
if (configurationElement != null) {
handler = (IHandler) configurationElement.createExecutableExtension(handlerAttributeName);
handler.addHandlerListener(getHandlerListener());
setEnabled(evaluationService == null ? null : evaluationService.getCurrentState());
refreshElements();
return true;
}
} catch (final ClassCastException e) {
final String message = "The proxied handler was the wrong class"; //$NON-NLS-1$
final IStatus status = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
WorkbenchPlugin.log(message, status);
configurationElement = null;
loadException = e;
} catch (final CoreException e) {
final String message = "The proxied handler for '" + configurationElement.getAttribute(handlerAttributeName) //$NON-NLS-1$
+ "' could not be loaded"; //$NON-NLS-1$
final IStatus status = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
WorkbenchPlugin.log(message, status);
configurationElement = null;
loadException = e;
}
return false;
}
return true;
}
private IHandlerListener getHandlerListener() {
if (handlerListener == null) {
handlerListener = new IHandlerListener() {
public void handlerChanged(final HandlerEvent handlerEvent) {
fireHandlerChanged(new HandlerEvent(HandlerProxy.this, handlerEvent.isEnabledChanged(), handlerEvent.isHandledChanged()));
}
};
}
return handlerListener;
}
@Override
public final String toString() {
if (handler == null) {
if (configurationElement != null) {
final String configurationElementAttribute = getConfigurationElementAttribute();
if (configurationElementAttribute != null) {
return configurationElementAttribute;
}
}
return "HandlerProxy()"; //$NON-NLS-1$
}
return handler.toString();
}
/**
* Retrives the ConfigurationElement attribute according to the <code>handlerAttributeName</code>.
*
* @return the handlerAttributeName value, may be <code>null</code>.
*/
private String getConfigurationElementAttribute() {
final String attribute = configurationElement.getAttribute(handlerAttributeName);
if (attribute == null) {
final IConfigurationElement[] children = configurationElement.getChildren(handlerAttributeName);
for (final IConfigurationElement element : children) {
final String childAttribute = element.getAttribute(IWorkbenchRegistryConstants.ATT_CLASS);
if (childAttribute != null) {
return childAttribute;
}
}
}
return attribute;
}
private boolean isOkToLoad() {
// if (PlatformUI.getWorkbench().isClosing()) {
// return handler != null;
// }
if (configurationElement != null && handler == null) {
final String bundleId = configurationElement.getContributor().getName();
return BundleUtility.isActive(bundleId);
}
return true;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.commands.IElementUpdater#updateElement(org.eclipse.ui.menus.UIElement, java.util.Map)
*/
public void updateElement(final UIElement element, final Map parameters) {
if (checkedState != null) {
final Boolean value = (Boolean) checkedState.getValue();
element.setChecked(value.booleanValue());
} else if (radioState != null) {
final String value = (String) radioState.getValue();
final Object parameter = parameters.get(RadioState.PARAMETER_ID);
element.setChecked(value != null && value.equals(parameter));
}
if (handler != null && handler instanceof IElementUpdater) {
((IElementUpdater) handler).updateElement(element, parameters);
}
}
private void refreshElements() {
if (commandId == null || !(handler instanceof IElementUpdater) && (checkedState == null && radioState == null)) {
return;
}
final ICommandService cs = PlatformUI.getWorkbench().getService(ICommandService.class);
cs.refreshElements(commandId, null);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.IStateListener#handleStateChange(org.eclipse.core.commands.State, java.lang.Object)
*/
public void handleStateChange(final State state, final Object oldValue) {
if (state.getId().equals(RegistryToggleState.STATE_ID)) {
checkedState = state;
refreshElements();
} else if (state.getId().equals(RadioState.STATE_ID)) {
radioState = state;
refreshElements();
}
if (handler instanceof IStateListener) {
((IStateListener) handler).handleStateChange(state, oldValue);
}
}
/**
* @return the config element for use with the PDE framework.
*/
public IConfigurationElement getConfigurationElement() {
return configurationElement;
}
public String getAttributeName() {
return handlerAttributeName;
}
/**
* @return Returns the handler.
*/
public IHandler getHandler() {
return handler;
}
}