blob: c9067b5613b0aafb0bcce668ddfa51a10324e5a1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 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);
}
/**
* 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);
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);
}
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);
}
/**
* 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);
}
}
}