/******************************************************************************* * 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.services; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; import org.eclipse.core.expressions.EvaluationContext; import org.eclipse.core.expressions.Expression; import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.ISourceProvider; import org.eclipse.ui.ISourceProviderListener; import org.eclipse.ui.ISources; /** * <p> * Provides common functionality for evaluating expressions and listening to * {@link ISourceProvider} (i.e., the common event framework for commands). * </p> * <p> * This class is not intended for use outside of the * <code>org.eclipse.ui.workbench</code> plug-in. * </p> * * @since 3.2 * @see ISourceProvider * @see ISources * @see Expression * @see IEvaluationContext */ public abstract class ExpressionAuthority implements ISourceProviderListener { /** * The evaluation context instance to use when evaluating expression. This * context is shared, and so all calls into <code>sourceChanged</code> * must happen on the event thread. */ private final IEvaluationContext context; /** * The current state of this authority. This is a child of the * {@link #context} that has been given the selection as the default * variable. This value is cleared to <code>null</code> whenever the * selection changes. */ private IEvaluationContext currentState = null; /** * The collection of source providers used by this authority. This * collection is consulted whenever a contribution is made. This collection * only contains instances of <code>ISourceProvider</code>. */ private final Collection providers = new ArrayList(); /** * Constructs a new instance of <code>ExpressionAuthority</code>. */ protected ExpressionAuthority() { context = new EvaluationContext(null, this); context.setAllowPluginActivation(true); } /** * Adds a source provider to a list of providers to check when updating. * This also attaches this authority as a listener to the provider. * * @param provider * The provider to add; must not be <code>null</code>. */ public final void addSourceProvider(final ISourceProvider provider) { provider.addSourceProviderListener(this); providers.add(provider); // Update the current state. final Map currentState = provider.getCurrentState(); final Iterator variableItr = currentState.entrySet().iterator(); while (variableItr.hasNext()) { final Map.Entry entry = (Map.Entry) variableItr.next(); final String variableName = (String) entry.getKey(); final Object variableValue = entry.getValue(); /* * Bug 84056. If we update the active workbench window, then we risk * falling back to that shell when the active shell has registered * as "none". */ if ((variableName != null) && (!ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME .equals(variableName))) { changeVariable(variableName, variableValue); } } } /** * Removes all of the source provider listeners. Subclasses may extend, but * must not override. */ public void dispose() { final Iterator providerItr = providers.iterator(); while (providerItr.hasNext()) { final ISourceProvider provider = (ISourceProvider) providerItr .next(); provider.removeSourceProviderListener(this); } providers.clear(); } /** * Returns whether at least one of the <code>IEvaluationResultCache</code> * instances in <code>collection</code> evaluates to <code>true</code>. * * @param collection * The evaluation result caches to check; must not be * <code>null</code>, but may be empty. * @return <code>true</code> if there is at least one expression that * evaluates to <code>true</code>; <code>false</code> * otherwise. */ protected final boolean evaluate(final Collection collection) { final Iterator iterator = collection.iterator(); while (iterator.hasNext()) { final IEvaluationResultCache cache = (IEvaluationResultCache) iterator .next(); if (evaluate(cache)) { return true; } } return false; } /** * Returns whether the <code>IEvaluationResultCache</code> evaluates to * <code>true</code>. * * @param expression * The evaluation result cache to check; must not be * <code>null</code>. * @return <code>true</code> if the expression evaluates to * <code>true</code>; <code>false</code> otherwise. */ protected final boolean evaluate(final IEvaluationResultCache expression) { final IEvaluationContext contextWithDefaultVariable = getCurrentState(); return expression.evaluate(contextWithDefaultVariable); } /** * Creates a new evaluation context based on the current evaluation context * (i.e., the current state), and places the current selection as the * default variable. * * @return An evaluation context that can be used for evaluating * expressions; never <code>null</code>. */ public final IEvaluationContext getCurrentState() { if (currentState == null) { final Object defaultVariable = context .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME); final IEvaluationContext contextWithDefaultVariable; if (defaultVariable instanceof IStructuredSelection) { final IStructuredSelection selection = (IStructuredSelection) defaultVariable; contextWithDefaultVariable = new EvaluationContext(context, selection.toList()); } else if ((defaultVariable instanceof ISelection) && (!((ISelection) defaultVariable).isEmpty())) { contextWithDefaultVariable = new EvaluationContext(context, Collections.singleton(defaultVariable)); } else { contextWithDefaultVariable = new EvaluationContext(context, Collections.EMPTY_LIST); } currentState = contextWithDefaultVariable; } return currentState; } /** * Returns the variable of the given name. * * @param name * The name of the variable to get; must not be <code>null</code>. * @return The variable of the given name; <code>null</code> if none. */ protected final Object getVariable(final String name) { return context.getVariable(name); } /** * Removes this source provider from the list, and detaches this authority * as a listener. * * @param provider * The provider to remove; must not be <code>null</code>. */ public final void removeSourceProvider(final ISourceProvider provider) { provider.removeSourceProviderListener(this); providers.remove(provider); final Map currentState = provider.getCurrentState(); final Iterator variableItr = currentState.entrySet().iterator(); while (variableItr.hasNext()) { final Map.Entry entry = (Map.Entry) variableItr.next(); final String variableName = (String) entry.getKey(); changeVariable(variableName, null); } } /** * Changes the variable of the given name. If the <code>value</code> is * <code>null</code>, then the variable is removed. * * @param name * The name of the variable to change; must not be * <code>null</code>. * @param value * The new value; the variable should be removed if this is * <code>null</code>. */ protected final void changeVariable(final String name, final Object value) { if (value == null) { context.removeVariable(name); } else { context.addVariable(name, value); } } /** * Carries out the actual source change notification. It assumed that by the * time this method is called, <code>getEvaluationContext()</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 abstract void sourceChanged(final int sourcePriority); /** * Similar to sourceChanged(int) this notifies the subclass about the * change, but using the array of source names that changed instead of the * priority ... int based. * <p> * Clients may override this method. * </p> * * @param sourceNames * The array of names that changed. * @since 3.3 */ protected void sourceChanged(final String[] sourceNames) { // this is a no-op, since we're late in the game } public final void sourceChanged(final int sourcePriority, final Map sourceValuesByName) { // If the selection has changed, invalidate the current state. if (sourceValuesByName .containsKey(ISources.ACTIVE_CURRENT_SELECTION_NAME)) { currentState = null; } final Iterator entryItr = sourceValuesByName.entrySet().iterator(); while (entryItr.hasNext()) { final Map.Entry entry = (Map.Entry) entryItr.next(); final String sourceName = (String) entry.getKey(); final Object sourceValue = entry.getValue(); updateEvaluationContext(sourceName, sourceValue); } sourceChanged(sourcePriority, (String[]) sourceValuesByName.keySet() .toArray(new String[0])); } public final void sourceChanged(final int sourcePriority, final String sourceName, final Object sourceValue) { // If the selection has changed, invalidate the current state. if (ISources.ACTIVE_CURRENT_SELECTION_NAME.equals(sourceName)) { currentState = null; } updateEvaluationContext(sourceName, sourceValue); sourceChanged(sourcePriority, new String[] { sourceName }); } /** * @param sourcePriority * @param strings */ private void sourceChanged(int sourcePriority, String[] sourceNames) { sourceChanged(sourcePriority); sourceChanged(sourceNames); } /** * Updates the evaluation context with the current state from all of the * source providers. */ protected final void updateCurrentState() { final Iterator providerItr = providers.iterator(); while (providerItr.hasNext()) { final ISourceProvider provider = (ISourceProvider) providerItr .next(); final Map currentState = provider.getCurrentState(); final Iterator variableItr = currentState.entrySet().iterator(); while (variableItr.hasNext()) { final Map.Entry entry = (Map.Entry) variableItr.next(); final String variableName = (String) entry.getKey(); final Object variableValue = entry.getValue(); /* * Bug 84056. If we update the active workbench window, then we * risk falling back to that shell when the active shell has * registered as "none". */ if ((variableName != null) && (!ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME .equals(variableName))) { changeVariable(variableName, variableValue); } } } } /** * Updates this authority's evaluation context. * * @param name * The name of the variable to update; must not be * <code>null</code>. * @param value * The new value of the variable. If this value is * <code>null</code>, then the variable is removed. */ protected void updateEvaluationContext(final String name, final Object value) { if (name != null) { changeVariable(name, value); } } }