blob: f110d1964c101046486fa7eaed9098eac9762f5f [file] [log] [blame]
/*******************************************************************************
* 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);
}
}