/******************************************************************************* * Copyright (c) 2007, 2008 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.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.commands.util.Tracing; import org.eclipse.core.expressions.Expression; import org.eclipse.core.expressions.ExpressionInfo; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.ISources; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.misc.Policy; import org.eclipse.ui.services.IEvaluationReference; import org.eclipse.ui.services.IEvaluationService; /** * @since 3.3 * */ public class EvaluationAuthority extends ExpressionAuthority { /** * */ private static final String COMPONENT = "EVALUATION"; //$NON-NLS-1$ /** * A bucket sort of the evaluation references based on source priority. Each * reference will appear only once per set, but may appear in multiple sets. * If no references are defined for a particular priority level, then the * array at that index will only contain <code>null</code>. */ private final Map cachesBySourceName = new HashMap(); private ListenerList serviceListeners = new ListenerList(); private int notifying = 0; // private final Map cachesByExpression = new HashMap(); public void addEvaluationListener(IEvaluationReference ref) { // we update the source priority bucket sort of activations. String[] sourceNames = getNames(ref); for (int i = 0; i < sourceNames.length; i++) { Map cachesByExpression = (HashMap) cachesBySourceName .get(sourceNames[i]); if (cachesByExpression == null) { cachesByExpression = new HashMap(1); cachesBySourceName.put(sourceNames[i], cachesByExpression); } final Expression expression = ref.getExpression(); Set caches = (Set) cachesByExpression.get(expression); if (caches == null) { caches = new HashSet(); cachesByExpression.put(expression, caches); } caches.add(ref); } boolean result = evaluate(ref); firePropertyChange(ref, null, valueOf(result)); } private Boolean valueOf(boolean result) { return result ? Boolean.TRUE : Boolean.FALSE; } private String[] getNames(IEvaluationReference ref) { ExpressionInfo info = new ExpressionInfo(); ref.getExpression().collectExpressionInfo(info); ArrayList allNames = new ArrayList(Arrays.asList(info .getAccessedVariableNames())); if (info.hasDefaultVariableAccess()) { allNames.add(ISources.ACTIVE_CURRENT_SELECTION_NAME); } allNames.addAll(Arrays.asList(info.getAccessedPropertyNames())); return (String[]) allNames.toArray(new String[allNames.size()]); } /* * (non-Javadoc) * * @see org.eclipse.ui.internal.services.ExpressionAuthority#sourceChanged(int) */ protected void sourceChanged(int sourcePriority) { // no-op, we want the other one } /* * (non-Javadoc) * * @see org.eclipse.ui.internal.services.ExpressionAuthority#sourceChanged(java.lang.String[]) */ protected void sourceChanged(String[] sourceNames) { startSourceChange(sourceNames); try { // evaluations to recompute for (int i = 0; i < sourceNames.length; i++) { Map cachesByExpression = (HashMap) cachesBySourceName .get(sourceNames[i]); if (cachesByExpression != null) { Collection v = cachesByExpression.values(); Set[] expressionCaches = (Set[]) v .toArray(new Set[v.size()]); for (int j = 0; j < expressionCaches.length; j++) { if (expressionCaches[j].size() > 0) { EvaluationReference[] refs = (EvaluationReference[]) expressionCaches[j] .toArray(new EvaluationReference[expressionCaches[j] .size()]); refsWithSameExpression(refs); } } } } } finally { endSourceChange(sourceNames); } } /** * This will evaluate all refs with the same expression. * * @param refs */ private void refsWithSameExpression(EvaluationReference[] refs) { int k = 0; while (k < refs.length && !refs[k].isPostingChanges()) { k++; } if (k >= refs.length) { return; } EvaluationReference ref = refs[k]; boolean oldValue = evaluate(ref); ref.clearResult(); final boolean newValue = evaluate(ref); if (oldValue != newValue) { firePropertyChange(ref, valueOf(oldValue), valueOf(newValue)); } for (k++; k < refs.length; k++) { ref = refs[k]; // this is not as expensive as it looks if (ref.isPostingChanges()) { oldValue = evaluate(ref); if (oldValue != newValue) { ref.setResult(newValue); firePropertyChange(ref, valueOf(oldValue), valueOf(newValue)); } } } } /** * @param sourceNames */ private void startSourceChange(final String[] sourceNames) { if (Policy.DEBUG_SOURCES) { Tracing.printTrace(COMPONENT, "start source changed: " //$NON-NLS-1$ + Arrays.asList(sourceNames)); } notifying++; if (notifying == 1) { fireServiceChange(IEvaluationService.PROP_NOTIFYING, Boolean.FALSE, Boolean.TRUE); } } /** * @param sourceNames */ private void endSourceChange(final String[] sourceNames) { if (Policy.DEBUG_SOURCES) { Tracing.printTrace(COMPONENT, "end source changed: " //$NON-NLS-1$ + Arrays.asList(sourceNames)); } if (notifying == 1) { fireServiceChange(IEvaluationService.PROP_NOTIFYING, Boolean.TRUE, Boolean.FALSE); } notifying--; } /** * @param ref */ public void removeEvaluationListener(IEvaluationReference ref) { // Next we update the source priority bucket sort of activations. String[] sourceNames = getNames(ref); for (int i = 0; i < sourceNames.length; i++) { Map cachesByExpression = (HashMap) cachesBySourceName .get(sourceNames[i]); if (cachesByExpression != null) { Set caches = (Set) cachesByExpression.get(ref.getExpression()); if (caches != null) { caches.remove(ref); if (caches.isEmpty()) { cachesByExpression.remove(ref.getExpression()); } } if (cachesByExpression.isEmpty()) { cachesBySourceName.remove(sourceNames[i]); } } } boolean result = evaluate(ref); firePropertyChange(ref, valueOf(result), null); } /** * @param ref * @param oldValue * @param newValue */ private void firePropertyChange(IEvaluationReference ref, Object oldValue, Object newValue) { ref.getListener().propertyChange( new PropertyChangeEvent(ref, ref.getProperty(), oldValue, newValue)); } private void fireServiceChange(final String property, final Object oldValue, final Object newValue) { Object[] listeners = serviceListeners.getListeners(); for (int i = 0; i < listeners.length; i++) { final IPropertyChangeListener listener = (IPropertyChangeListener) listeners[i]; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { WorkbenchPlugin.log(exception); } public void run() throws Exception { listener.propertyChange(new PropertyChangeEvent( EvaluationAuthority.this, property, oldValue, newValue)); } }); } } /** * @param listener */ public void addServiceListener(IPropertyChangeListener listener) { serviceListeners.add(listener); } /** * @param listener */ public void removeServiceListener(IPropertyChangeListener listener) { serviceListeners.remove(listener); } /** * <p> * Bug 95792. A mechanism by which the key binding architecture can force an * update of the handlers (based on the active shell) before trying to * execute a command. This mechanism is required for GTK+ only. * </p> * <p> * DO NOT CALL THIS METHOD. * </p> */ final void updateShellKludge() { updateCurrentState(); sourceChanged(new String[] { ISources.ACTIVE_SHELL_NAME }); } /** * Returns the currently active shell. * * @return The currently active shell; may be <code>null</code>. */ final Shell getActiveShell() { return (Shell) getVariable(ISources.ACTIVE_SHELL_NAME); } }