001: /*******************************************************************************
002: * Copyright (c) 2005, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.services;
011:
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.Collections;
015: import java.util.Iterator;
016: import java.util.Map;
017:
018: import org.eclipse.core.expressions.EvaluationContext;
019: import org.eclipse.core.expressions.Expression;
020: import org.eclipse.core.expressions.IEvaluationContext;
021: import org.eclipse.jface.viewers.ISelection;
022: import org.eclipse.jface.viewers.IStructuredSelection;
023: import org.eclipse.ui.ISourceProvider;
024: import org.eclipse.ui.ISourceProviderListener;
025: import org.eclipse.ui.ISources;
026:
027: /**
028: * <p>
029: * Provides common functionality for evaluating expressions and listening to
030: * {@link ISourceProvider} (i.e., the common event framework for commands).
031: * </p>
032: * <p>
033: * This class is not intended for use outside of the
034: * <code>org.eclipse.ui.workbench</code> plug-in.
035: * </p>
036: *
037: * @since 3.2
038: * @see ISourceProvider
039: * @see ISources
040: * @see Expression
041: * @see IEvaluationContext
042: */
043: public abstract class ExpressionAuthority implements
044: ISourceProviderListener {
045:
046: /**
047: * The evaluation context instance to use when evaluating expression. This
048: * context is shared, and so all calls into <code>sourceChanged</code>
049: * must happen on the event thread.
050: */
051: private final IEvaluationContext context;
052:
053: /**
054: * The current state of this authority. This is a child of the
055: * {@link #context} that has been given the selection as the default
056: * variable. This value is cleared to <code>null</code> whenever the
057: * selection changes.
058: */
059: private IEvaluationContext currentState = null;
060:
061: /**
062: * The collection of source providers used by this authority. This
063: * collection is consulted whenever a contribution is made. This collection
064: * only contains instances of <code>ISourceProvider</code>.
065: */
066: private final Collection providers = new ArrayList();
067:
068: /**
069: * Constructs a new instance of <code>ExpressionAuthority</code>.
070: */
071: protected ExpressionAuthority() {
072: context = new EvaluationContext(null, this );
073: context.setAllowPluginActivation(true);
074: }
075:
076: /**
077: * Adds a source provider to a list of providers to check when updating.
078: * This also attaches this authority as a listener to the provider.
079: *
080: * @param provider
081: * The provider to add; must not be <code>null</code>.
082: */
083: public final void addSourceProvider(final ISourceProvider provider) {
084: provider.addSourceProviderListener(this );
085: providers.add(provider);
086:
087: // Update the current state.
088: final Map currentState = provider.getCurrentState();
089: final Iterator variableItr = currentState.entrySet().iterator();
090: while (variableItr.hasNext()) {
091: final Map.Entry entry = (Map.Entry) variableItr.next();
092: final String variableName = (String) entry.getKey();
093: final Object variableValue = entry.getValue();
094:
095: /*
096: * Bug 84056. If we update the active workbench window, then we risk
097: * falling back to that shell when the active shell has registered
098: * as "none".
099: */
100: if ((variableName != null)
101: && (!ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME
102: .equals(variableName))) {
103: changeVariable(variableName, variableValue);
104: }
105: }
106: }
107:
108: /**
109: * Removes all of the source provider listeners. Subclasses may extend, but
110: * must not override.
111: */
112: public void dispose() {
113: final Iterator providerItr = providers.iterator();
114: while (providerItr.hasNext()) {
115: final ISourceProvider provider = (ISourceProvider) providerItr
116: .next();
117: provider.removeSourceProviderListener(this );
118: }
119:
120: providers.clear();
121: }
122:
123: /**
124: * Returns whether at least one of the <code>IEvaluationResultCache</code>
125: * instances in <code>collection</code> evaluates to <code>true</code>.
126: *
127: * @param collection
128: * The evaluation result caches to check; must not be
129: * <code>null</code>, but may be empty.
130: * @return <code>true</code> if there is at least one expression that
131: * evaluates to <code>true</code>; <code>false</code>
132: * otherwise.
133: */
134: protected final boolean evaluate(final Collection collection) {
135: final Iterator iterator = collection.iterator();
136: while (iterator.hasNext()) {
137: final IEvaluationResultCache cache = (IEvaluationResultCache) iterator
138: .next();
139: if (evaluate(cache)) {
140: return true;
141: }
142: }
143:
144: return false;
145: }
146:
147: /**
148: * Returns whether the <code>IEvaluationResultCache</code> evaluates to
149: * <code>true</code>.
150: *
151: * @param expression
152: * The evaluation result cache to check; must not be
153: * <code>null</code>.
154: * @return <code>true</code> if the expression evaluates to
155: * <code>true</code>; <code>false</code> otherwise.
156: */
157: protected final boolean evaluate(
158: final IEvaluationResultCache expression) {
159: final IEvaluationContext contextWithDefaultVariable = getCurrentState();
160: return expression.evaluate(contextWithDefaultVariable);
161: }
162:
163: /**
164: * Creates a new evaluation context based on the current evaluation context
165: * (i.e., the current state), and places the current selection as the
166: * default variable.
167: *
168: * @return An evaluation context that can be used for evaluating
169: * expressions; never <code>null</code>.
170: */
171: public final IEvaluationContext getCurrentState() {
172: if (currentState == null) {
173: final Object defaultVariable = context
174: .getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
175: final IEvaluationContext contextWithDefaultVariable;
176: if (defaultVariable instanceof IStructuredSelection) {
177: final IStructuredSelection selection = (IStructuredSelection) defaultVariable;
178: contextWithDefaultVariable = new EvaluationContext(
179: context, selection.toList());
180: } else if ((defaultVariable instanceof ISelection)
181: && (!((ISelection) defaultVariable).isEmpty())) {
182: contextWithDefaultVariable = new EvaluationContext(
183: context, Collections.singleton(defaultVariable));
184: } else {
185: contextWithDefaultVariable = new EvaluationContext(
186: context, Collections.EMPTY_LIST);
187: }
188: currentState = contextWithDefaultVariable;
189: }
190:
191: return currentState;
192: }
193:
194: /**
195: * Returns the variable of the given name.
196: *
197: * @param name
198: * The name of the variable to get; must not be <code>null</code>.
199: * @return The variable of the given name; <code>null</code> if none.
200: */
201: protected final Object getVariable(final String name) {
202: return context.getVariable(name);
203: }
204:
205: /**
206: * Removes this source provider from the list, and detaches this authority
207: * as a listener.
208: *
209: * @param provider
210: * The provider to remove; must not be <code>null</code>.
211: */
212: public final void removeSourceProvider(
213: final ISourceProvider provider) {
214: provider.removeSourceProviderListener(this );
215: providers.remove(provider);
216:
217: final Map currentState = provider.getCurrentState();
218: final Iterator variableItr = currentState.entrySet().iterator();
219: while (variableItr.hasNext()) {
220: final Map.Entry entry = (Map.Entry) variableItr.next();
221: final String variableName = (String) entry.getKey();
222: changeVariable(variableName, null);
223: }
224: }
225:
226: /**
227: * Changes the variable of the given name. If the <code>value</code> is
228: * <code>null</code>, then the variable is removed.
229: *
230: * @param name
231: * The name of the variable to change; must not be
232: * <code>null</code>.
233: * @param value
234: * The new value; the variable should be removed if this is
235: * <code>null</code>.
236: */
237: protected final void changeVariable(final String name,
238: final Object value) {
239: if (value == null) {
240: context.removeVariable(name);
241: } else {
242: context.addVariable(name, value);
243: }
244: }
245:
246: /**
247: * Carries out the actual source change notification. It assumed that by the
248: * time this method is called, <code>getEvaluationContext()</code> is
249: * up-to-date with the current state of the application.
250: *
251: * @param sourcePriority
252: * A bit mask of all the source priorities that have changed.
253: */
254: protected abstract void sourceChanged(final int sourcePriority);
255:
256: /**
257: * Similar to sourceChanged(int) this notifies the subclass about the
258: * change, but using the array of source names that changed instead of the
259: * priority ... int based.
260: * <p>
261: * Clients may override this method.
262: * </p>
263: *
264: * @param sourceNames
265: * The array of names that changed.
266: * @since 3.3
267: */
268: protected void sourceChanged(final String[] sourceNames) {
269: // this is a no-op, since we're late in the game
270: }
271:
272: public final void sourceChanged(final int sourcePriority,
273: final Map sourceValuesByName) {
274: // If the selection has changed, invalidate the current state.
275: if (sourceValuesByName
276: .containsKey(ISources.ACTIVE_CURRENT_SELECTION_NAME)) {
277: currentState = null;
278: }
279:
280: final Iterator entryItr = sourceValuesByName.entrySet()
281: .iterator();
282: while (entryItr.hasNext()) {
283: final Map.Entry entry = (Map.Entry) entryItr.next();
284: final String sourceName = (String) entry.getKey();
285: final Object sourceValue = entry.getValue();
286: updateEvaluationContext(sourceName, sourceValue);
287: }
288: sourceChanged(sourcePriority, (String[]) sourceValuesByName
289: .keySet().toArray(new String[0]));
290: }
291:
292: public final void sourceChanged(final int sourcePriority,
293: final String sourceName, final Object sourceValue) {
294: // If the selection has changed, invalidate the current state.
295: if (ISources.ACTIVE_CURRENT_SELECTION_NAME.equals(sourceName)) {
296: currentState = null;
297: }
298:
299: updateEvaluationContext(sourceName, sourceValue);
300: sourceChanged(sourcePriority, new String[] { sourceName });
301: }
302:
303: /**
304: * @param sourcePriority
305: * @param strings
306: */
307: private void sourceChanged(int sourcePriority, String[] sourceNames) {
308: sourceChanged(sourcePriority);
309: sourceChanged(sourceNames);
310: }
311:
312: /**
313: * Updates the evaluation context with the current state from all of the
314: * source providers.
315: */
316: protected final void updateCurrentState() {
317: final Iterator providerItr = providers.iterator();
318: while (providerItr.hasNext()) {
319: final ISourceProvider provider = (ISourceProvider) providerItr
320: .next();
321: final Map currentState = provider.getCurrentState();
322: final Iterator variableItr = currentState.entrySet()
323: .iterator();
324: while (variableItr.hasNext()) {
325: final Map.Entry entry = (Map.Entry) variableItr.next();
326: final String variableName = (String) entry.getKey();
327: final Object variableValue = entry.getValue();
328: /*
329: * Bug 84056. If we update the active workbench window, then we
330: * risk falling back to that shell when the active shell has
331: * registered as "none".
332: */
333: if ((variableName != null)
334: && (!ISources.ACTIVE_WORKBENCH_WINDOW_SHELL_NAME
335: .equals(variableName))) {
336: changeVariable(variableName, variableValue);
337: }
338: }
339: }
340: }
341:
342: /**
343: * Updates this authority's evaluation context.
344: *
345: * @param name
346: * The name of the variable to update; must not be
347: * <code>null</code>.
348: * @param value
349: * The new value of the variable. If this value is
350: * <code>null</code>, then the variable is removed.
351: */
352: protected void updateEvaluationContext(final String name,
353: final Object value) {
354: if (name != null) {
355: changeVariable(name, value);
356: }
357: }
358: }
|