Skip to main content
aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorEugene Tarassov2011-12-15 22:03:09 +0000
committerEugene Tarassov2011-12-15 22:03:09 +0000
commitda70cd9b8c31a12eca0969fa1bbf657e1945360a (patch)
tree96eabe792c81797ae2aec27bec56074a78602ab6 /tests
parent649b46a48f0a085976144d6a191f9726d1df566e (diff)
downloadorg.eclipse.tcf-da70cd9b8c31a12eca0969fa1bbf657e1945360a.tar.gz
org.eclipse.tcf-da70cd9b8c31a12eca0969fa1bbf657e1945360a.tar.xz
org.eclipse.tcf-da70cd9b8c31a12eca0969fa1bbf657e1945360a.zip
Bug 349998 - [test] JUnit tests for debug UI
Diffstat (limited to 'tests')
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/.classpath7
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/.project28
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs8
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF16
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/build.properties4
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java605
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java50
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java55
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java162
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java83
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java162
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java207
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java104
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java80
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java649
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java50
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java126
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java45
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java424
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java57
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java177
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java61
22 files changed, 3160 insertions, 0 deletions
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/.classpath b/tests/plugins/org.eclipse.tcf.debug.test/.classpath
new file mode 100644
index 000000000..64c5e31b7
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/.project b/tests/plugins/org.eclipse.tcf.debug.test/.project
new file mode 100644
index 000000000..114675c19
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.tcf.debug.test</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs b/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..9d7f7d27a
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Thu May 26 13:10:48 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF b/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..48627ce45
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/META-INF/MANIFEST.MF
@@ -0,0 +1,16 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Test
+Bundle-SymbolicName: org.eclipse.tcf.debug.test
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.eclipse.tcf.debug.test.Activator
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.tcf.debug,
+ org.eclipse.tcf.debug.ui,
+ org.eclipse.tcf.core,
+ org.junit;bundle-version="3.8.2",
+ org.eclipse.debug.ui;bundle-version="3.7.0"
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Import-Package: org.eclipse.debug.internal.ui.viewers.model
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/build.properties b/tests/plugins/org.eclipse.tcf.debug.test/build.properties
new file mode 100644
index 000000000..34d2e4d2d
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java
new file mode 100644
index 000000000..efd2294a2
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/AbstractTcfUITest.java
@@ -0,0 +1,605 @@
+package org.eclipse.tcf.debug.test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import junit.framework.TestCase;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.debug.core.DebugException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfigurationType;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.debug.core.ILaunchesListener2;
+import org.eclipse.debug.core.model.IDisconnect;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer;
+import org.eclipse.debug.ui.IDebugUIConstants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.tcf.core.TransientPeer;
+import org.eclipse.tcf.debug.test.util.AggregateCallback;
+import org.eclipse.tcf.debug.test.util.Callback;
+import org.eclipse.tcf.debug.test.util.DataCallback;
+import org.eclipse.tcf.debug.test.util.Query;
+import org.eclipse.tcf.debug.test.util.Task;
+import org.eclipse.tcf.debug.test.util.Callback.ICanceledListener;
+import org.eclipse.tcf.protocol.IChannel;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.protocol.Protocol;
+import org.eclipse.tcf.services.IBreakpoints;
+import org.eclipse.tcf.services.IDiagnostics;
+import org.eclipse.tcf.services.IDiagnostics.ISymbol;
+import org.eclipse.tcf.services.IExpressions;
+import org.eclipse.tcf.services.IRunControl;
+import org.eclipse.tcf.services.IRunControl.RunControlContext;
+import org.eclipse.tcf.services.IStackTrace;
+import org.eclipse.tcf.services.ISymbols;
+import org.junit.Assert;
+
+/**
+ * Base test for validating TCF Debugger UI.
+ */
+@SuppressWarnings("restriction")
+public abstract class AbstractTcfUITest extends TestCase implements IViewerUpdatesListenerConstants {
+
+ private final static int NUM_CHANNELS = 1;
+
+ protected IChannel[] channels;
+
+ private Query<Object> fMonitorChannelQuery;
+ private List<Throwable> errors = new ArrayList<Throwable>();
+ private IPeer peer;
+ protected ILaunch fLaunch;
+
+ protected VirtualTreeModelViewer fDebugViewViewer;
+ protected TestDebugContextProvider fDebugContextProvider;
+ protected VirtualViewerUpdatesListener fDebugViewListener;
+ protected VariablesVirtualTreeModelViewer fVariablesViewViewer;
+ protected VirtualViewerUpdatesListener fVariablesViewListener;
+ protected VariablesVirtualTreeModelViewer fRegistersViewViewer;
+ protected VirtualViewerUpdatesListener fRegistersViewListener;
+
+ protected IDiagnostics diag;
+ protected IExpressions expr;
+ protected ISymbols syms;
+ protected IStackTrace stk;
+ protected IRunControl rc;
+ protected IBreakpoints bp;
+
+ protected TestRunControlListener fRcListener;
+
+
+ private static class RemotePeer extends TransientPeer {
+ private final ArrayList<Map<String,String>> attrs;
+
+ public RemotePeer(ArrayList<Map<String,String>> attrs) {
+ super(attrs.get(0));
+ this.attrs = attrs;
+ }
+
+ public IChannel openChannel() {
+ assert Protocol.isDispatchThread();
+ IChannel c = super.openChannel();
+ for (int i = 1; i < attrs.size(); i++) c.redirect(attrs.get(i));
+ return c;
+ }
+ }
+
+ private static IPeer getPeer(String[] arr) {
+ ArrayList<Map<String,String>> l = new ArrayList<Map<String,String>>();
+ for (String s : arr) {
+ Map<String,String> map = new HashMap<String,String>();
+ int len = s.length();
+ int i = 0;
+ while (i < len) {
+ int i0 = i;
+ while (i < len && s.charAt(i) != '=' && s.charAt(i) != 0) i++;
+ int i1 = i;
+ if (i < len && s.charAt(i) == '=') i++;
+ int i2 = i;
+ while (i < len && s.charAt(i) != ':') i++;
+ int i3 = i;
+ if (i < len && s.charAt(i) == ':') i++;
+ String key = s.substring(i0, i1);
+ String val = s.substring(i2, i3);
+ map.put(key, val);
+ }
+ l.add(map);
+ }
+ return new RemotePeer(l);
+ }
+
+ protected void setUp() throws Exception {
+
+ createDebugViewViewer();
+ createLaunch();
+
+ // Command line should contain peer description string, for example:
+ // "ID=Test:TransportName=TCP:Host=127.0.0.1:Port=1534"
+ final String[] args = new String[] { "TransportName=TCP:Host=127.0.0.1:Port=1534" };
+ if (args.length < 1) {
+ System.err.println("Missing command line argument - peer identification string");
+ System.exit(4);
+ }
+
+ peer = new Query<IPeer>() {
+ @Override
+ protected void execute(DataCallback<IPeer> callback) {
+ callback.setData(getPeer(args));
+ callback.done();
+ }
+ }.get();
+
+ channels = new IChannel[NUM_CHANNELS];
+
+ new Query<Object>() {
+ @Override
+ protected void execute(DataCallback<Object> callback) {
+ try {
+ openChannels(peer, callback);
+ }
+ catch (Throwable x) {
+ errors.add(x);
+ int cnt = 0;
+ for (int i = 0; i < channels.length; i++) {
+ if (channels[i] == null) continue;
+ if (channels[i].getState() != IChannel.STATE_CLOSED) channels[i].close();
+ cnt++;
+ }
+ if (cnt == 0) {
+ callback.setError(errors.get(0));
+ callback.done();
+ }
+ }
+ }
+ }.get();
+
+ getRemoteServices();
+
+ validateTestAvailable();
+
+ new Task<Object>() {
+ @Override
+ public Object call() throws Exception {
+ setUpServiceListeners();
+ return null;
+ }
+ }.get();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ new Task<Object>() {
+ @Override
+ public Object call() throws Exception {
+ tearDownServiceListeners();
+ return null;
+ }
+ }.get();
+
+ terminateLaunch();
+ disposeDebugViewViewer();
+
+ new Query<Object>() {
+ @Override
+ protected void execute(DataCallback<Object> callback) {
+ closeChannels(callback);
+ }
+ }.get();
+
+ // Check for listener errors at the end of tearDown.
+ fRcListener.checkError();
+ }
+
+ protected String getDiagnosticsTestName() {
+ return "RCBP1";
+ }
+
+ protected void setUpServiceListeners() throws Exception{
+ fRcListener = new TestRunControlListener(rc);
+ }
+
+ protected void tearDownServiceListeners() throws Exception{
+ fRcListener.dispose();
+ }
+
+ private void createDebugViewViewer() {
+ final Display display = Display.getDefault();
+ display.syncExec(new Runnable() {
+ public void run() {
+ fDebugViewViewer = new VirtualTreeModelViewer(display, SWT.NONE, new PresentationContext(IDebugUIConstants.ID_DEBUG_VIEW));
+ fDebugViewViewer.setInput(DebugPlugin.getDefault().getLaunchManager());
+ fDebugViewViewer.setAutoExpandLevel(-1);
+ fDebugViewListener = new VirtualViewerUpdatesListener(fDebugViewViewer);
+ fDebugContextProvider = new TestDebugContextProvider(fDebugViewViewer);
+ fVariablesViewViewer = new VariablesVirtualTreeModelViewer(IDebugUIConstants.ID_VARIABLE_VIEW, fDebugContextProvider);
+ fVariablesViewListener = new VirtualViewerUpdatesListener(fVariablesViewViewer);
+ fRegistersViewViewer = new VariablesVirtualTreeModelViewer(IDebugUIConstants.ID_REGISTER_VIEW, fDebugContextProvider);
+ fRegistersViewListener = new VirtualViewerUpdatesListener(fRegistersViewViewer);
+ }
+ });
+ }
+
+ private void disposeDebugViewViewer() {
+ final Display display = Display.getDefault();
+ display.syncExec(new Runnable() {
+ public void run() {
+ fDebugViewListener.dispose();
+ fDebugContextProvider.dispose();
+ fDebugViewViewer.dispose();
+ fVariablesViewListener.dispose();
+ fVariablesViewViewer.dispose();
+ fRegistersViewListener.dispose();
+ fRegistersViewViewer.dispose();
+ }
+ });
+
+ }
+
+ private void createLaunch() throws CoreException {
+ ILaunchManager lManager = DebugPlugin.getDefault().getLaunchManager();
+ ILaunchConfigurationType lcType = lManager.getLaunchConfigurationType("org.eclipse.tcf.debug.LaunchConfigurationType");
+ ILaunchConfigurationWorkingCopy lcWc = lcType.newInstance(null, "test");
+ lcWc.doSave();
+ fLaunch = lcWc.launch("debug", new NullProgressMonitor());
+ Assert.assertTrue( fLaunch instanceof IDisconnect );
+ }
+
+ private void terminateLaunch() throws DebugException, InterruptedException, ExecutionException {
+ ((IDisconnect)fLaunch).disconnect();
+
+ new Query<Object>() {
+ @Override
+ protected void execute(final DataCallback<Object> callback) {
+ final ILaunchManager lm = DebugPlugin.getDefault().getLaunchManager();
+
+ final AtomicBoolean callbackDone = new AtomicBoolean(false);
+ ILaunchesListener2 disconnectListener = new ILaunchesListener2() {
+ public void launchesAdded(ILaunch[] launches) {}
+ public void launchesChanged(ILaunch[] launches) {}
+ public void launchesRemoved(ILaunch[] launches) {}
+ public void launchesTerminated(ILaunch[] launches) {
+ if (Arrays.asList(launches).contains(fLaunch)) {
+ if (!callbackDone.getAndSet(true)) {
+ lm.removeLaunchListener(this);
+ callback.done();
+ }
+ }
+ }
+ };
+ lm.addLaunchListener(disconnectListener);
+ if (((IDisconnect)fLaunch).isDisconnected() && !callbackDone.getAndSet(true)) {
+ lm.removeLaunchListener(disconnectListener);
+ callback.done();
+
+ }
+ }
+ }.get();
+ }
+
+ private void getRemoteServices() {
+ assert !Protocol.isDispatchThread();
+ Protocol.invokeAndWait(new Runnable() {
+ public void run() {
+ diag = channels[0].getRemoteService(IDiagnostics.class);
+ expr = channels[0].getRemoteService(IExpressions.class);
+ syms = channels[0].getRemoteService(ISymbols.class);
+ stk = channels[0].getRemoteService(IStackTrace.class);
+ rc = channels[0].getRemoteService(IRunControl.class);
+ bp = channels[0].getRemoteService(IBreakpoints.class);
+ };
+ });
+ }
+
+ private void openChannels(IPeer peer, Callback callback) {
+ assert Protocol.isDispatchThread();
+
+ for (int i = 0; i < channels.length; i++) {
+ channels[i] = peer.openChannel();
+ }
+ monitorChannels(
+ new Callback(callback) {
+ @Override
+ protected void handleSuccess() {
+ fMonitorChannelQuery = new Query<Object>() {
+ protected void execute(org.eclipse.tcf.debug.test.util.DataCallback<Object> callback) {
+ monitorChannels(callback, true);
+ };
+ };
+ fMonitorChannelQuery.invoke();
+ super.handleSuccess();
+ }
+ },
+ false);
+ }
+
+ private void closeChannels(final Callback callback) {
+ assert Protocol.isDispatchThread();
+ fMonitorChannelQuery.cancel(false);
+ try {
+ fMonitorChannelQuery.get();
+ } catch (ExecutionException e) {
+ callback.setError(e.getCause());
+ } catch (CancellationException e) {
+ // expected
+ } catch (InterruptedException e) {
+ }
+
+ for (int i = 0; i < channels.length; i++) {
+ if (channels[i].getState() != IChannel.STATE_CLOSED) channels[i].close();
+ }
+ monitorChannels(callback, true);
+ }
+
+ private static class ChannelMonitorListener implements IChannel.IChannelListener {
+
+ final IChannel fChannel;
+ final boolean fClose;
+ final Callback fCallback;
+ private boolean fActive = true;
+
+ private class CancelListener implements ICanceledListener {
+ public void requestCanceled(Callback rm) {
+ Protocol.invokeLater(new Runnable() {
+ public void run() {
+ if (deactivate()) {
+ fCallback.done();
+ }
+ }
+ });
+ }
+ }
+
+ private boolean deactivate() {
+ if (fActive) {
+ fChannel.removeChannelListener(ChannelMonitorListener.this);
+ fActive = false;
+ return true;
+ }
+ return false;
+ }
+
+ ChannelMonitorListener (IChannel channel, Callback cb, boolean close) {
+ fCallback = cb;
+ fClose = close;
+ fChannel = channel;
+ fChannel.addChannelListener(this);
+ fCallback.addCancelListener(new CancelListener());
+ }
+
+ public void onChannelOpened() {
+ if (!deactivate()) return;
+
+ fChannel.removeChannelListener(this);
+ fCallback.done();
+ }
+
+ public void congestionLevel(int level) {
+ }
+
+ public void onChannelClosed(Throwable error) {
+ if (!deactivate()) return;
+
+ if (!fClose) {
+ fCallback.setError( new IOException("Remote peer closed connection before all tests finished") );
+ } else {
+ fCallback.setError(error);
+ }
+ fCallback.done();
+ }
+ }
+
+ protected void monitorChannels(final Callback callback, final boolean close) {
+ assert Protocol.isDispatchThread();
+
+ AggregateCallback acb = new AggregateCallback(callback);
+ int count = 0;
+ for (int i = 0; i < channels.length; i++) {
+ if (!checkChannelsState(channels[i], close)) {
+ new ChannelMonitorListener(channels[i], new Callback(acb), close);
+ count++;
+ }
+ }
+ acb.setDoneCount(count);
+ }
+
+ // Checks whether all channels have achieved the desired state.
+ private boolean checkChannelsState(IChannel channel, boolean close) {
+ if (close) {
+ if (channel.getState() != IChannel.STATE_CLOSED) {
+ return false;
+ }
+ } else {
+ if (channel.getState() != IChannel.STATE_OPEN) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void validateTestAvailable() throws ExecutionException, InterruptedException {
+ String[] testList = getDiagnosticsTestList();
+
+ int i = 0;
+ for (; i < testList.length; i++) {
+ if ("RCBP1".equals(testList[i])) break;
+ }
+
+ Assert.assertTrue("Required test not supported", i != testList.length);
+ }
+
+ protected String[] getDiagnosticsTestList() throws ExecutionException, InterruptedException {
+ assert !Protocol.isDispatchThread();
+ return new Query<String[]>() {
+ @Override
+ protected void execute(final DataCallback<String[]> callback) {
+ diag.getTestList(new IDiagnostics.DoneGetTestList() {
+ public void doneGetTestList(IToken token, Throwable error, String[] list) {
+ callback.setData(list);
+ callback.setError(error);
+ callback.done();
+ }
+ });
+
+ }
+ }.get();
+ }
+
+ protected void setBreakpoint(final String bp_id, final String location) throws InterruptedException, ExecutionException {
+ new Query<Object> () {
+ protected void execute(final DataCallback<Object> callback) {
+ Map<String,Object> m = new HashMap<String,Object>();
+ m.put(IBreakpoints.PROP_ID, bp_id);
+ m.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE);
+ m.put(IBreakpoints.PROP_LOCATION, location);
+ bp.set(new Map[]{ m }, new IBreakpoints.DoneCommand() {
+ public void doneCommand(IToken token, Exception error) {
+ callback.setError(error);
+ callback.done();
+ }
+ });
+ }
+ }.get();
+ }
+
+ protected String startDiagnosticsTest() throws InterruptedException, ExecutionException {
+ return new Query<String> () {
+ protected void execute(final DataCallback<String> callback) {
+ diag.runTest(getDiagnosticsTestName(), new IDiagnostics.DoneRunTest() {
+ public void doneRunTest(IToken token, Throwable error, String id) {
+ callback.setData(id);
+ callback.setError(error);
+ callback.done();
+ }
+ });
+ }
+ }.get();
+ }
+
+ protected RunControlContext getRunControlContext(final String contextId) throws InterruptedException, ExecutionException {
+ return new Query<RunControlContext> () {
+ @Override
+ protected void execute(final DataCallback<RunControlContext> callback) {
+ rc.getContext(contextId, new IRunControl.DoneGetContext() {
+ public void doneGetContext(IToken token, Exception error, IRunControl.RunControlContext ctx) {
+ callback.setData(ctx);
+ callback.setError(error);
+ callback.done();
+ }
+ });
+ }
+ }.get();
+ }
+
+ protected String getProcessIdFromRunControlContext(final RunControlContext rcContext) throws InterruptedException, ExecutionException {
+ return new Task<String>() {
+ @Override
+ public String call() throws Exception {
+ return rcContext.getProcessID();
+ }
+ }.get();
+ }
+
+ protected String getSingleThreadIdFromProcess(final String processId) throws InterruptedException, ExecutionException {
+ return new Query<String> () {
+ protected void execute(final DataCallback<String> callback) {
+ rc.getChildren(processId, new IRunControl.DoneGetChildren() {
+ public void doneGetChildren(IToken token, Exception error, String[] ids) {
+ if (error != null) {
+ callback.setError(error);
+ }
+ else if (ids == null || ids.length == 0) {
+ callback.setError(new Exception("Test process has no threads"));
+ }
+ else if (ids.length != 1) {
+ callback.setError(new Exception("Test process has too many threads"));
+ }
+ else {
+ callback.setData(ids[0]);
+ }
+ callback.done();
+ }
+ });
+ }
+ }.get();
+ }
+
+ protected boolean getRunControlContextHasState(final RunControlContext rcContext) throws InterruptedException, ExecutionException {
+ return new Task<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return rcContext.hasState();
+ }
+ }.get();
+ }
+
+ protected ISymbol getDiagnosticsSymbol(final String processId, final String testFunction) throws InterruptedException, ExecutionException {
+ return new Query<ISymbol>() {
+ @Override
+ protected void execute(final DataCallback<ISymbol> callback) {
+ diag.getSymbol(processId, testFunction, new IDiagnostics.DoneGetSymbol() {
+ public void doneGetSymbol(IToken token, Throwable error, IDiagnostics.ISymbol symbol) {
+ if (error != null) {
+ callback.setError(error);
+ }
+ else if (symbol == null) {
+ callback.setError(new Exception("Symbol must not be null: tcf_test_func3"));
+ }
+ else {
+ callback.setData(symbol);
+ }
+ callback.done();
+ }
+ });
+ }
+ }.get();
+ }
+
+ protected Number getSymbolValue(final ISymbol symbol) throws InterruptedException, ExecutionException {
+ return new Task<Number>() {
+ @Override
+ public Number call() throws Exception {
+ return symbol.getValue();
+ }
+ }.get();
+ }
+
+ protected void resumeContext(final RunControlContext rcContext, final int mode) throws InterruptedException, ExecutionException {
+ new Query<Object>() {
+ @Override
+ protected void execute(final DataCallback<Object> callback) {
+ rcContext.resume(mode, 1, new IRunControl.DoneCommand() {
+ public void doneCommand(IToken token, Exception error) {
+ callback.setError(error);
+ callback.done();
+ }
+ });
+ }
+ }.get();
+ }
+
+ protected void resumeAndWaitForSuspend(final RunControlContext rcContext, final int mode) throws InterruptedException, ExecutionException {
+ Query<String> suspendQuery = new Query<String> () {
+ @Override
+ protected void execute(DataCallback<String> callback) {
+ fRcListener.addWaitingForSuspend(rcContext.getID(), callback);
+ }
+ };
+ suspendQuery.invoke();
+ resumeContext(rcContext, mode);
+ suspendQuery.get();
+ }
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java
new file mode 100644
index 000000000..5f6ce2de6
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/Activator.java
@@ -0,0 +1,50 @@
+package org.eclipse.tcf.debug.test;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.tcf.debug.test"; //$NON-NLS-1$
+
+ // The shared instance
+ private static Activator plugin;
+
+ /**
+ * The constructor
+ */
+ public Activator() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java
new file mode 100644
index 000000000..fb8995fc2
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/IViewerUpdatesListenerConstants.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+/**
+ * Copied from org.eclipse.cdt.tests.dsf.
+ *
+ * Convenience interface with constants used by the test model update listener.
+ */
+public interface IViewerUpdatesListenerConstants {
+
+ public static final int LABEL_SEQUENCE_COMPLETE = 0X00000001;
+ public static final int CONTENT_SEQUENCE_COMPLETE = 0X00000002;
+ public static final int CONTENT_SEQUENCE_STARTED = 0X00020000;
+ public static final int LABEL_UPDATES = 0X00000004;
+ public static final int LABEL_SEQUENCE_STARTED = 0X00040000;
+ public static final int HAS_CHILDREN_UPDATES = 0X00000008;
+ public static final int HAS_CHILDREN_UPDATES_STARTED = 0X00080000;
+ public static final int CHILD_COUNT_UPDATES = 0X00000010;
+ public static final int CHILD_COUNT_UPDATES_STARTED = 0X00100000;
+ public static final int CHILDREN_UPDATES = 0X00000020;
+ public static final int CHILDREN_UPDATES_STARTED = 0X00200000;
+ public static final int MODEL_CHANGED_COMPLETE = 0X00000040;
+
+ /**
+ * Flag to check whether a model proxy was installed for the model. The model proxy installation is tracked by
+ * looking for the IModelDelta.EXPAND flag, since the model expands and selects threads when Debug view is opened.
+ */
+ public static final int MODEL_PROXIES_INSTALLED = 0X00000080;
+ public static final int STATE_SAVE_COMPLETE = 0X00000100;
+ public static final int STATE_SAVE_STARTED = 0X01000000;
+ public static final int STATE_RESTORE_COMPLETE = 0X00000200;
+ public static final int STATE_RESTORE_STARTED = 0X02000000;
+ public static final int STATE_UPDATES = 0X00000400;
+ public static final int STATE_UPDATES_STARTED = 0X04000000;
+
+ public static final int VIEWER_UPDATES_RUNNING = 0X00001000;
+ public static final int LABEL_UPDATES_RUNNING = 0X00002000;
+
+ public static final int VIEWER_UPDATES_STARTED = HAS_CHILDREN_UPDATES_STARTED | CHILD_COUNT_UPDATES_STARTED | CHILDREN_UPDATES_STARTED;
+
+ public static final int LABEL_COMPLETE = LABEL_SEQUENCE_COMPLETE | LABEL_UPDATES | LABEL_UPDATES_RUNNING;
+ public static final int CONTENT_UPDATES = HAS_CHILDREN_UPDATES | CHILD_COUNT_UPDATES | CHILDREN_UPDATES;
+ public static final int CONTENT_COMPLETE = CONTENT_UPDATES | CONTENT_SEQUENCE_COMPLETE | VIEWER_UPDATES_RUNNING;
+
+ public static final int ALL_UPDATES_COMPLETE = LABEL_COMPLETE | CONTENT_COMPLETE | MODEL_PROXIES_INSTALLED | LABEL_UPDATES_RUNNING | VIEWER_UPDATES_RUNNING;
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java
new file mode 100644
index 000000000..f370b43df
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/SampleTest.java
@@ -0,0 +1,162 @@
+package org.eclipse.tcf.debug.test;
+
+import java.math.BigInteger;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
+
+import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem;
+import org.eclipse.tcf.debug.test.util.DataCallback;
+import org.eclipse.tcf.debug.test.util.Query;
+import org.eclipse.tcf.debug.test.util.Task;
+import org.eclipse.tcf.services.IDiagnostics.ISymbol;
+import org.eclipse.tcf.services.IRunControl;
+import org.eclipse.tcf.services.IRunControl.RunControlContext;
+import org.junit.Assert;
+
+@SuppressWarnings("restriction")
+public class SampleTest extends AbstractTcfUITest
+{
+
+ private TestBreakpointsListener fBpListener;
+ private String fTestId;
+ private RunControlContext fTestCtx;
+ private String fProcessId;
+ private String fThreadId;
+ private RunControlContext fThreadCtx;
+
+ @Override
+ protected void setUpServiceListeners() throws Exception {
+ super.setUpServiceListeners();
+ fBpListener = new TestBreakpointsListener(bp);
+ }
+
+ @Override
+ protected void tearDownServiceListeners() throws Exception {
+ fBpListener.dispose();
+ super.tearDownServiceListeners();
+ }
+
+ private void createBreakpoint(String bpId, String testFunc) throws InterruptedException, ExecutionException {
+ fBpListener.setBreakpointId(bpId);
+ setBreakpoint(bpId, testFunc);
+ }
+
+ private void startProcess() throws InterruptedException, ExecutionException {
+ fTestId = startDiagnosticsTest();
+ fTestCtx = getRunControlContext(fTestId);
+ fProcessId = getProcessIdFromRunControlContext(fTestCtx);
+ fBpListener.setProcess(fProcessId);
+ fThreadId = null;
+ if (!fProcessId.equals(fTestId)) {
+ fThreadId = fTestId;
+ } else {
+ fThreadId = getSingleThreadIdFromProcess(fProcessId);
+ }
+ fThreadCtx = getRunControlContext(fThreadId);
+ Assert.assertTrue("Invalid thread context", getRunControlContextHasState(fThreadCtx));
+ }
+
+ protected String getProcessNameFromRunControlContext(final RunControlContext rcContext) throws InterruptedException, ExecutionException {
+ return new Task<String>() {
+ @Override
+ public String call() throws Exception {
+ return rcContext.getName();
+ }
+ }.get();
+ }
+
+ private void runToTestEntry(String testFunc) throws InterruptedException, ExecutionException {
+ final String suspended_pc = new Query<String> () {
+ @Override
+ protected void execute(DataCallback<String> callback) {
+ fRcListener.waitForSuspend(fThreadCtx, callback);
+ }
+ }.get();
+ ISymbol sym_func0 = getDiagnosticsSymbol(fProcessId, testFunc);
+ String sym_func0_value = getSymbolValue(sym_func0).toString();
+ if (!new BigInteger(sym_func0_value).equals(new BigInteger(suspended_pc))) {
+ resumeAndWaitForSuspend(fThreadCtx, IRunControl.RM_RESUME);
+ }
+ }
+
+ private void initProcessModel(String bpId, String testFunc) throws Exception {
+ createBreakpoint(bpId, testFunc);
+
+ fDebugViewListener.reset();
+ fDebugViewListener.setDelayContentUntilProxyInstall(true);
+ startProcess();
+ fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | MODEL_PROXIES_INSTALLED | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING);
+ runToTestEntry(testFunc);
+ }
+
+ public void testDebugViewContent() throws Exception {
+ initProcessModel("TestStepBP", "tcf_test_func0");
+
+ VirtualItem launchItem = fDebugViewListener.findElement(new Pattern[] { Pattern.compile(".*" + fLaunch.getLaunchConfiguration().getName() + ".*") } );
+ Assert.assertTrue(launchItem != null);
+
+ VirtualItem processItem = fDebugViewListener.findElement(launchItem, new Pattern[] { Pattern.compile(".*") } );
+ Assert.assertTrue(processItem != null);
+
+ VirtualItem threadItem = fDebugViewListener.findElement(processItem, new Pattern[] { Pattern.compile(".*" + fThreadId + ".*") } );
+ Assert.assertTrue(threadItem != null);
+
+ VirtualItem frameItem = fDebugViewListener.findElement(threadItem, new Pattern[] { Pattern.compile(".*")});
+ Assert.assertTrue(frameItem != null);
+
+ fBpListener.checkError();
+ }
+
+ public void testSteppingDebugViewOnly() throws Exception {
+ initProcessModel("TestStepBP", "tcf_test_func0");
+
+ // Execute step loop
+ String previousThreadLabel = null;
+ for (int stepNum = 0; stepNum < 100; stepNum++) {
+ fDebugViewListener.reset();
+
+ resumeAndWaitForSuspend(fThreadCtx, IRunControl.RM_STEP_INTO_LINE);
+
+ fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING);
+ VirtualItem topFrameItem = fDebugViewListener.findElement(
+ new Pattern[] { Pattern.compile(".*"), Pattern.compile(".*"), Pattern.compile(".*" + fProcessId + ".*\\(Step.*"), Pattern.compile(".*")});
+ Assert.assertTrue(topFrameItem != null);
+ String topFrameLabel = ((String[])topFrameItem.getData(VirtualItem.LABEL_KEY))[0];
+ Assert.assertTrue(!topFrameLabel.equals(previousThreadLabel));
+ previousThreadLabel = topFrameLabel;
+ }
+
+ fBpListener.checkError();
+ }
+
+ public void testSteppingWithVariablesAndRegisters() throws Exception {
+ fVariablesViewViewer.setActive(true);
+ fRegistersViewViewer.setActive(true);
+
+ initProcessModel("TestStepBP", "tcf_test_func0");
+
+ // Execute step loop
+ String previousThreadLabel = null;
+ for (int stepNum = 0; stepNum < 100; stepNum++) {
+ fDebugViewListener.reset();
+ fVariablesViewListener.reset();
+ fRegistersViewListener.reset();
+
+ resumeAndWaitForSuspend(fThreadCtx, IRunControl.RM_STEP_INTO_LINE);
+
+ fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING);
+ fVariablesViewListener.waitTillFinished(CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING);
+ fRegistersViewListener.waitTillFinished(CONTENT_SEQUENCE_COMPLETE | LABEL_UPDATES_RUNNING);
+ VirtualItem topFrameItem = fDebugViewListener.findElement(
+ new Pattern[] { Pattern.compile(".*"), Pattern.compile(".*"), Pattern.compile(".*" + fProcessId + ".*\\(Step.*"), Pattern.compile(".*")});
+ Assert.assertTrue(topFrameItem != null);
+ String topFrameLabel = ((String[])topFrameItem.getData(VirtualItem.LABEL_KEY))[0];
+ Assert.assertTrue(!topFrameLabel.equals(previousThreadLabel));
+ previousThreadLabel = topFrameLabel;
+ }
+
+ fBpListener.checkError();
+ }
+
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java
new file mode 100644
index 000000000..d84512fb2
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestBreakpointsListener.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.tcf.services.IBreakpoints;
+
+/**
+ * Listener for breakpoint service events.
+ */
+public class TestBreakpointsListener implements IBreakpoints.BreakpointsListener{
+
+ final private IBreakpoints fBreakpoints;
+ private Throwable fError;
+ private String bp_id;
+ private String process_id;
+ private boolean test_done = false;
+
+ public TestBreakpointsListener(IBreakpoints bp) {
+ fBreakpoints = bp;
+ fBreakpoints.addListener(this);
+ }
+
+ public void dispose() {
+ test_done = true;
+ fBreakpoints.removeListener(this);
+ }
+
+ public void setBreakpointId(String bpId) {
+ bp_id = bpId;
+ }
+
+ public void setProcess(String processId) {
+ process_id = processId;
+ }
+
+ public void checkError() throws Exception {
+ if (fError != null) {
+ throw new Exception(fError);
+ }
+ }
+
+ public void exit(Throwable error) {
+ fError = error;
+ }
+
+ public void breakpointStatusChanged(String id, Map<String, Object> status) {
+ if (id.equals(bp_id) && process_id != null && !test_done) {
+ String s = (String)status.get(IBreakpoints.STATUS_ERROR);
+ if (s != null) exit(new Exception("Invalid BP status: " + s));
+ Collection<Map<String,Object>> list = (Collection<Map<String,Object>>)status.get(IBreakpoints.STATUS_INSTANCES);
+ if (list == null) return;
+ String err = null;
+ for (Map<String,Object> map : list) {
+ String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT);
+ if (process_id.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null)
+ err = (String)map.get(IBreakpoints.INSTANCE_ERROR);
+ }
+ if (err != null) exit(new Exception("Invalid BP status: " + err));
+ }
+ }
+
+ public void contextAdded(Map<String, Object>[] bps) {
+ }
+
+ public void contextChanged(Map<String, Object>[] bps) {
+ }
+
+ public void contextRemoved(String[] ids) {
+ }
+
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java
new file mode 100644
index 000000000..7d127ab7a
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestDebugContextProvider.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 Wind River Systems, Inc. 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import java.util.ArrayList;
+
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
+import org.eclipse.debug.ui.contexts.AbstractDebugContextProvider;
+import org.eclipse.debug.ui.contexts.DebugContextEvent;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Copied from org.eclipse.debug.internal.ui.views.launch.LaunchView class.
+ *
+ * This debug context provider emulates the Debug view debug context
+ * provider behavior.
+ */
+@SuppressWarnings("restriction")
+public class TestDebugContextProvider extends AbstractDebugContextProvider implements IModelChangedListener, ISelectionChangedListener{
+
+ private ISelection fContext = null;
+ private ITreeModelViewer fViewer = null;
+ private Visitor fVisitor = new Visitor();
+
+ class Visitor implements IModelDeltaVisitor {
+ public boolean visit(IModelDelta delta, int depth) {
+ if ((delta.getFlags() & (IModelDelta.STATE | IModelDelta.CONTENT)) > 0) {
+ // state and/or content change
+ if ((delta.getFlags() & IModelDelta.SELECT) == 0) {
+ // no select flag
+ if ((delta.getFlags() & IModelDelta.CONTENT) > 0) {
+ // content has changed without select >> possible re-activation
+ possibleChange(getViewerTreePath(delta), DebugContextEvent.ACTIVATED);
+ } else if ((delta.getFlags() & IModelDelta.STATE) > 0) {
+ // state has changed without select >> possible state change of active context
+ possibleChange(getViewerTreePath(delta), DebugContextEvent.STATE);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Returns a tree path for the node, *not* including the root element.
+ *
+ * @param node
+ * model delta
+ * @return corresponding tree path
+ */
+ private TreePath getViewerTreePath(IModelDelta node) {
+ ArrayList<Object> list = new ArrayList<Object>();
+ IModelDelta parentDelta = node.getParentDelta();
+ while (parentDelta != null) {
+ list.add(0, node.getElement());
+ node = parentDelta;
+ parentDelta = node.getParentDelta();
+ }
+ return new TreePath(list.toArray());
+ }
+
+ public TestDebugContextProvider(ITreeModelViewer viewer) {
+ super(null);
+ fViewer = viewer;
+ fViewer.addModelChangedListener(this);
+ fViewer.addSelectionChangedListener(this);
+ }
+
+ protected void dispose() {
+ fContext = null;
+ fViewer.removeModelChangedListener(this);
+ fViewer.removeSelectionChangedListener(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.ui.contexts.IDebugContextProvider#getActiveContext()
+ */
+ public synchronized ISelection getActiveContext() {
+ return fContext;
+ }
+
+ protected void activate(ISelection selection) {
+ synchronized (this) {
+ fContext = selection;
+ }
+ fire(new DebugContextEvent(this, selection, DebugContextEvent.ACTIVATED));
+ }
+
+ protected void possibleChange(TreePath element, int type) {
+ DebugContextEvent event = null;
+ synchronized (this) {
+ if (fContext instanceof ITreeSelection) {
+ ITreeSelection ss = (ITreeSelection) fContext;
+ if (ss.size() == 1) {
+ TreePath current = ss.getPaths()[0];
+ if (current.startsWith(element, null)) {
+ if (current.getSegmentCount() == element.getSegmentCount()) {
+ event = new DebugContextEvent(this, fContext, type);
+ } else {
+ // if parent of the currently selected element
+ // changes, issue event to update STATE only
+ event = new DebugContextEvent(this, fContext, DebugContextEvent.STATE);
+ }
+ }
+ }
+ }
+ }
+ if (event == null) {
+ return;
+ }
+ if (Display.getDefault().getThread() == Thread.currentThread()) {
+ fire(event);
+ } else {
+ final DebugContextEvent finalEvent = event;
+ Display.getDefault().asyncExec(new Runnable() {
+ public void run() {
+ // verify selection is still the same context since job was scheduled
+ synchronized (TestDebugContextProvider.this) {
+ if (fContext instanceof IStructuredSelection) {
+ IStructuredSelection ss = (IStructuredSelection) fContext;
+ Object changed = ((IStructuredSelection)finalEvent.getContext()).getFirstElement();
+ if (!(ss.size() == 1 && ss.getFirstElement().equals(changed))) {
+ return;
+ }
+ }
+ }
+ fire(finalEvent);
+ }
+ });
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener#modelChanged(org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta)
+ */
+ public void modelChanged(IModelDelta delta, IModelProxy proxy) {
+ delta.accept(fVisitor);
+ }
+
+ public void selectionChanged(SelectionChangedEvent event) {
+ activate(event.getSelection());
+ }
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java
new file mode 100644
index 000000000..fb5b04704
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestRunControlListener.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2011 Wind River Systems, Inc. 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.debug.internal.ui.actions.RunContextualLaunchAction;
+import org.eclipse.tcf.debug.test.util.DataCallback;
+import org.eclipse.tcf.protocol.IErrorReport;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.services.IRunControl;
+import org.eclipse.tcf.services.IRunControl.RunControlContext;
+
+/**
+ * Run control service listener. Allows client to wait for events with a
+ * callback.
+ */
+public class TestRunControlListener implements IRunControl.RunControlListener {
+
+ private IRunControl fRunControl;
+ private Throwable fError;
+
+ private final HashMap<String, IRunControl.RunControlContext> ctx_map = new HashMap<String,IRunControl.RunControlContext>();
+ private final HashMap<String, String> fSuspendedPCs = new HashMap<String, String>();
+ private Map<String, List<DataCallback<String>>> waiting_suspend = new HashMap<String, List<DataCallback<String>>>();
+ private String process_id;
+ private boolean test_done;
+ private String test_ctx_id;
+
+
+ public TestRunControlListener(IRunControl rc) {
+ fRunControl = rc;
+ fRunControl.addListener(this);
+ }
+
+ public void dispose() throws Exception {
+ test_done = true;
+ fRunControl.removeListener(this);
+ }
+
+ public void checkError() throws Exception {
+ if (fError != null) {
+ throw new Exception(fError);
+ }
+ }
+
+ public void waitForSuspend(RunControlContext context, DataCallback<String> cb) {
+ final String contextId = context.getID();
+ if (fSuspendedPCs.containsKey(contextId)) {
+ String pc = fSuspendedPCs.get(contextId);
+ if (pc != null) {
+ cb.setData(pc);
+ cb.done();
+ return;
+ } else {
+ addWaitingForSuspend(contextId, cb);
+ }
+ } else {
+ getContextState(context, cb);
+ }
+ }
+
+ private void getContextState(RunControlContext context, final DataCallback<String> cb) {
+ final String contextId = context.getID();
+ context.getState(new IRunControl.DoneGetState() {
+ public void doneGetState(IToken token, Exception error,
+ boolean suspended, String pc, String reason,
+ Map<String,Object> params) {
+ if (error != null) {
+ exit(error);
+ }
+ else if (suspended) {
+ fSuspendedPCs.put(contextId, pc);
+ cb.setData(pc);
+ cb.done();
+ }
+ else {
+ fSuspendedPCs.put(contextId, null);
+ if (cb != null) {
+ addWaitingForSuspend(contextId, cb);
+ }
+ }
+ }
+ });
+ }
+
+ public void addWaitingForSuspend(String contextId, DataCallback<String> cb) {
+ List<DataCallback<String>> waitingList = waiting_suspend.get(contextId);
+ if (waitingList == null) {
+ waitingList = new ArrayList<DataCallback<String>>(1);
+ waiting_suspend.put(contextId, waitingList);
+ }
+ waitingList.add(cb);
+ }
+
+ private void exit(Throwable e) {
+ fError = e;
+ }
+
+ public void contextAdded(RunControlContext[] contexts) {
+ for (IRunControl.RunControlContext ctx : contexts) {
+ if (ctx_map.get(ctx.getID()) != null) exit(new Error("Invalid 'contextAdded' event"));
+ ctx_map.put(ctx.getID(), ctx);
+ }
+ }
+
+ public void contextChanged(RunControlContext[] contexts) {
+ for (IRunControl.RunControlContext ctx : contexts) {
+ if (ctx_map.get(ctx.getID()) == null) return;
+ ctx_map.put(ctx.getID(), ctx);
+ }
+ }
+
+ public void waitSuspended(DataCallback<String> cb) {
+
+ }
+
+ public void contextRemoved(String[] context_ids) {
+ for (String id : context_ids) {
+ ctx_map.remove(id);
+ if (id.equals(test_ctx_id)) {
+ if (test_done) {
+// bp.set(null, new IBreakpoints.DoneCommand() {
+// public void doneCommand(IToken token, Exception error) {
+// exit(error);
+// }
+// });
+ }
+ else {
+ exit(new Exception("Test process exited too soon"));
+ }
+ return;
+ }
+ }
+ }
+
+ public void contextSuspended(String contextId, String pc, String reason, Map<String, Object> params) {
+ if (pc == null || "".equals(pc)) {
+ RunControlContext context = ctx_map.get(contextId);
+ if (context != null) {
+ getContextState(context, null);
+ }
+ return;
+ }
+
+ fSuspendedPCs.put(contextId, pc);
+ List<DataCallback<String>> waitingList = waiting_suspend.remove(contextId);
+ if (waitingList != null) {
+ for (DataCallback<String> cb : waitingList) {
+ cb.setData(pc);
+ cb.done();
+ }
+ }
+ if (test_done) {
+ IRunControl.RunControlContext ctx = ctx_map.get(contextId);
+ if (ctx != null && process_id != null && process_id.equals(ctx.getParentID())) {
+ ctx.resume(IRunControl.RM_RESUME, 1, new IRunControl.DoneCommand() {
+ public void doneCommand(IToken token, Exception error) {
+ if (error instanceof IErrorReport) {
+ int code = ((IErrorReport)error).getErrorCode();
+ if (code == IErrorReport.TCF_ERROR_ALREADY_RUNNING) return;
+ if (code == IErrorReport.TCF_ERROR_INV_CONTEXT) return;
+ }
+ if (error != null) exit(error);
+ }
+ });
+ }
+ }
+ }
+
+ public void contextResumed(String context) {
+ fSuspendedPCs.put(context, null);
+ }
+
+ public void containerSuspended(String context, String pc, String reason, Map<String, Object> params,
+ String[] suspended_ids)
+ {
+ for (String id : suspended_ids) {
+ assert id != null;
+ contextSuspended(id, id.equals(context) ? pc : null, null, null);
+ }
+ }
+
+ public void containerResumed(String[] resumed_ids) {
+ for (String id : resumed_ids) {
+ assert id != null;
+ contextResumed(id);
+ }
+ }
+
+ public void contextException(String context, String msg) {
+ exit(new Exception("Context exception: " + msg));
+ }
+
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java
new file mode 100644
index 000000000..5b21d0dc4
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/TestViewerInputService.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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
+ * Wind River - Pawel Piech - NPE when closing the Variables view (Bug 213719)
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
+import org.eclipse.debug.internal.ui.viewers.model.ViewerAdapterService;
+import org.eclipse.debug.internal.ui.viewers.model.ViewerInputUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputProvider;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputRequestor;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate;
+
+/**
+ * Copied from org.eclipse.debug.internal.ui.viewers.model.provisional
+ * for customization.
+ *
+ * Service to compute a viewer input from a source object
+ * for a given presentation context.
+ */
+@SuppressWarnings("restriction")
+public class TestViewerInputService {
+
+ /**
+ * An input object which will yield a null input element.
+ *
+ * @since 3.6
+ */
+ public final static Object NULL_INPUT = new IViewerInputProvider() {
+ public void update(IViewerInputUpdate update) {
+ update.setInputElement(null);
+ update.done();
+ }
+ };
+
+ // previous update request, cancelled when a new request comes in
+ private IViewerInputUpdate fPendingUpdate = null;
+
+ private IViewerInputRequestor fRequestor = null;
+
+ private ITreeModelViewer fViewer;
+
+ private IViewerInputRequestor fProxyRequest = new IViewerInputRequestor() {
+ public void viewerInputComplete(final IViewerInputUpdate update) {
+ synchronized (TestViewerInputService.this) {
+ fPendingUpdate = null;
+ }
+ fRequestor.viewerInputComplete(update);
+ }
+ };
+
+ /**
+ * Constructs a viewer input service for the given requester and presentation context.
+ *
+ * @param requestor client requesting viewer inputs
+ * @param context context for which inputs are required
+ */
+ public TestViewerInputService(ITreeModelViewer viewer, IViewerInputRequestor requestor) {
+ fRequestor = requestor;
+ fViewer = viewer;
+ }
+
+ /**
+ * Resolves a viewer input derived from the given source object.
+ * Reports the result to the given this service's requester. A requester may be called back
+ * in the same or thread, or asynchronously in a different thread. Cancels any previous
+ * incomplete request from this service's requester.
+ *
+ * @param source source from which to derive a viewer input
+ */
+ public void resolveViewerInput(Object source) {
+ IViewerInputProvider provdier = ViewerAdapterService.getInputProvider(source);
+ synchronized (this) {
+ // cancel any pending update
+ if (fPendingUpdate != null) {
+ fPendingUpdate.cancel();
+ }
+ fPendingUpdate = new ViewerInputUpdate(fViewer.getPresentationContext(), fViewer.getInput(), fProxyRequest, source);
+ }
+ if (provdier == null) {
+ fPendingUpdate.setInputElement(source);
+ fPendingUpdate.done();
+ } else {
+ provdier.update(fPendingUpdate);
+ }
+ }
+
+ /**
+ * Disposes this viewer input service, canceling any pending jobs.
+ */
+ public synchronized void dispose() {
+ if (fPendingUpdate != null) {
+ fPendingUpdate.cancel();
+ fPendingUpdate = null;
+ }
+ }
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java
new file mode 100644
index 000000000..439a34b95
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VariablesVirtualTreeModelViewer.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputRequestor;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ViewerInputService;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer;
+import org.eclipse.debug.ui.contexts.DebugContextEvent;
+import org.eclipse.debug.ui.contexts.IDebugContextListener;
+import org.eclipse.debug.ui.contexts.IDebugContextProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ *
+ */
+@SuppressWarnings("restriction")
+public class VariablesVirtualTreeModelViewer extends VirtualTreeModelViewer implements IDebugContextListener {
+
+ private IDebugContextProvider fDebugContextProvider;
+ private ViewerInputService fInputService;
+ private boolean fActive = false;
+
+ public VariablesVirtualTreeModelViewer(String contextId, IDebugContextProvider debugContextProvider) {
+ super(Display.getDefault(), SWT.NONE, new PresentationContext(contextId));
+ fInputService = new ViewerInputService(this, new IViewerInputRequestor() {
+
+ public void viewerInputComplete(IViewerInputUpdate update) {
+ if (!update.isCanceled()) {
+ setInput(update.getInputElement());
+ }
+ }
+ });
+ fDebugContextProvider = debugContextProvider;
+ debugContextProvider.addDebugContextListener(this);
+ }
+
+ public void setActive(boolean active) {
+ if (fActive == active) {
+ return;
+ }
+ fActive = active;
+ if (fActive) {
+ setActiveContext(fDebugContextProvider.getActiveContext());
+ } else {
+ fInputService.resolveViewerInput(TestViewerInputService.NULL_INPUT);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ fDebugContextProvider.removeDebugContextListener(this);
+ fInputService.dispose();
+ super.dispose();
+ }
+
+ public void debugContextChanged(DebugContextEvent event) {
+ if (fActive && (event.getFlags() | DebugContextEvent.ACTIVATED) != 0) {
+ setActiveContext(event.getContext());
+ }
+ }
+
+ private void setActiveContext(ISelection selection) {
+ if (selection instanceof IStructuredSelection) {
+ fInputService.resolveViewerInput(((IStructuredSelection)selection).getFirstElement());
+ }
+ }
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java
new file mode 100644
index 000000000..6001fd2ab
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/ViewerUpdatesListener.java
@@ -0,0 +1,649 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import junit.framework.Assert;
+
+import org.eclipse.debug.internal.ui.viewers.model.ILabelUpdateListener;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
+import org.eclipse.jface.viewers.TreePath;
+
+/**
+ * Copied from org.eclipse.cdt.tests.dsf
+ *
+ */
+@SuppressWarnings("restriction")
+public class ViewerUpdatesListener
+ implements IViewerUpdateListener, ILabelUpdateListener, IModelChangedListener, IViewerUpdatesListenerConstants,
+ IStateUpdateListener
+{
+ private ITreeModelViewer fViewer;
+
+ private boolean fFailOnRedundantUpdates;
+ private Set<IViewerUpdate> fRedundantUpdates = new HashSet<IViewerUpdate>();
+
+ private boolean fFailOnMultipleModelUpdateSequences;
+ private boolean fMultipleModelUpdateSequencesObserved;
+ private boolean fFailOnMultipleLabelUpdateSequences;
+ private boolean fMultipleLabelUpdateSequencesObserved;
+
+ private boolean fDelayContentUntilProxyInstall;
+
+ private Set<TreePath> fHasChildrenUpdatesScheduled = makeTreePathSet();
+ private Set<IViewerUpdate> fHasChildrenUpdatesRunning = new HashSet<IViewerUpdate>();
+ private Set<IViewerUpdate> fHasChildrenUpdatesCompleted = new HashSet<IViewerUpdate>();
+ private Map<TreePath, Set<Integer>> fChildrenUpdatesScheduled = makeTreePathMap();
+ private Set<IViewerUpdate> fChildrenUpdatesRunning = new HashSet<IViewerUpdate>();
+ private Set<IViewerUpdate> fChildrenUpdatesCompleted = new HashSet<IViewerUpdate>();
+ private Set<TreePath> fChildCountUpdatesScheduled = makeTreePathSet();
+ private Set<IViewerUpdate> fChildCountUpdatesRunning = new HashSet<IViewerUpdate>();
+ private Set<IViewerUpdate> fChildCountUpdatesCompleted = new HashSet<IViewerUpdate>();
+ private Set<TreePath> fLabelUpdates = makeTreePathSet();
+ private Set<IViewerUpdate> fLabelUpdatesRunning = new HashSet<IViewerUpdate>();
+ private Set<IViewerUpdate> fLabelUpdatesCompleted = new HashSet<IViewerUpdate>();
+ private Set<TreePath> fPropertiesUpdates = makeTreePathSet();
+ private boolean fModelProxyInstalled;
+ private Set<TreePath> fStateUpdates = makeTreePathSet();
+ private boolean fContentSequenceStarted;
+ private boolean fContentSequenceComplete;
+ private boolean fLabelUpdatesStarted;
+ private boolean fLabelSequenceComplete;
+ private boolean fModelChangedComplete;
+ private boolean fStateSaveStarted;
+ private boolean fStateSaveComplete;
+ private boolean fStateRestoreStarted;
+ private boolean fStateRestoreComplete;
+ private int fContentUpdatesCounter;
+ private int fLabelUpdatesCounter;
+ private int fTimeoutInterval = 60000;
+ private long fTimeoutTime;
+
+ protected Set<TreePath> makeTreePathSet() {
+ return new HashSet<TreePath>();
+ }
+
+ protected <V> Map<TreePath, V> makeTreePathMap() {
+ return new HashMap<TreePath, V>();
+ }
+
+ public ViewerUpdatesListener(ITreeModelViewer viewer, boolean failOnRedundantUpdates, boolean failOnMultipleModelUpdateSequences) {
+ this(viewer);
+ setFailOnRedundantUpdates(failOnRedundantUpdates);
+ setFailOnMultipleModelUpdateSequences(failOnMultipleModelUpdateSequences);
+ }
+
+ public ViewerUpdatesListener() {
+ // No viewer to register with. Client will have to register the listener manually.
+ }
+
+ public ViewerUpdatesListener(ITreeModelViewer viewer) {
+ fViewer = viewer;
+ fViewer.addLabelUpdateListener(this);
+ fViewer.addModelChangedListener(this);
+ fViewer.addStateUpdateListener(this);
+ fViewer.addViewerUpdateListener(this);
+ }
+
+ public void dispose() {
+ if (fViewer != null) {
+ fViewer.removeLabelUpdateListener(this);
+ fViewer.removeModelChangedListener(this);
+ fViewer.removeStateUpdateListener(this);
+ fViewer.removeViewerUpdateListener(this);
+ fViewer = null;
+ }
+ }
+
+
+ public void setFailOnRedundantUpdates(boolean failOnRedundantUpdates) {
+ fFailOnRedundantUpdates = failOnRedundantUpdates;
+ }
+
+ public void setFailOnMultipleModelUpdateSequences(boolean failOnMultipleLabelUpdateSequences) {
+ fFailOnMultipleModelUpdateSequences = failOnMultipleLabelUpdateSequences;
+ }
+
+ public void setFailOnMultipleLabelUpdateSequences(boolean failOnMultipleLabelUpdateSequences) {
+ fFailOnMultipleLabelUpdateSequences = failOnMultipleLabelUpdateSequences;
+ }
+
+ /**
+ * Causes the content sequence started/ended notifications to be ignored
+ * until the model proxy is installed. This flag has to be set again after
+ * every reset.
+ * @param delay If true, listener will delay.
+ */
+ public void setDelayContentUntilProxyInstall(boolean delay) {
+ fDelayContentUntilProxyInstall = delay;
+ }
+
+ /**
+ * Sets the the maximum amount of time (in milliseconds) that the update listener
+ * is going to wait. If set to -1, the listener will wait indefinitely.
+ */
+ public void setTimeoutInterval(int milis) {
+ fTimeoutInterval = milis;
+ }
+
+ public void reset() {
+ fRedundantUpdates.clear();
+ fMultipleLabelUpdateSequencesObserved = false;
+ fMultipleModelUpdateSequencesObserved = false;
+ fHasChildrenUpdatesScheduled.clear();
+ fHasChildrenUpdatesRunning.clear();
+ fHasChildrenUpdatesCompleted.clear();
+ fChildrenUpdatesScheduled.clear();
+ fChildrenUpdatesRunning.clear();
+ fChildrenUpdatesCompleted.clear();
+ fChildCountUpdatesScheduled.clear();
+ fChildCountUpdatesRunning.clear();
+ fChildCountUpdatesCompleted.clear();
+ fLabelUpdates.clear();
+ fLabelUpdatesRunning.clear();
+ fLabelUpdatesCompleted.clear();
+ fModelProxyInstalled = false;
+ fContentSequenceStarted = false;
+ fContentSequenceComplete = false;
+ fLabelUpdatesStarted = false;
+ fLabelSequenceComplete = false;
+ fStateSaveStarted = false;
+ fStateSaveComplete = false;
+ fStateRestoreStarted = false;
+ fStateRestoreComplete = false;
+ fDelayContentUntilProxyInstall = false;
+
+ fTimeoutTime = System.currentTimeMillis() + fTimeoutInterval;
+ resetModelChanged();
+ }
+
+ public void resetModelChanged() {
+ fModelChangedComplete = false;
+ }
+
+ public void addHasChildrenUpdate(TreePath path) {
+ fHasChildrenUpdatesScheduled.add(path);
+ }
+
+ public void removeHasChildrenUpdate(TreePath path) {
+ fHasChildrenUpdatesScheduled.remove(path);
+ }
+
+ public void addChildCountUpdate(TreePath path) {
+ fChildCountUpdatesScheduled.add(path);
+ }
+
+ public void removeChildreCountUpdate(TreePath path) {
+ fChildCountUpdatesScheduled.remove(path);
+ }
+
+ public void addChildreUpdate(TreePath path, int index) {
+ Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(path);
+ if (childrenIndexes == null) {
+ childrenIndexes = new TreeSet<Integer>();
+ fChildrenUpdatesScheduled.put(path, childrenIndexes);
+ }
+ childrenIndexes.add(new Integer(index));
+ }
+
+ public void removeChildrenUpdate(TreePath path, int index) {
+ Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(path);
+ if (childrenIndexes != null) {
+ childrenIndexes.remove(new Integer(index));
+ if (childrenIndexes.isEmpty()) {
+ fChildrenUpdatesScheduled.remove(path);
+ }
+ }
+ }
+
+ public void addLabelUpdate(TreePath path) {
+ fLabelUpdates.add(path);
+ }
+
+ public void addPropertiesUpdate(TreePath path) {
+ fPropertiesUpdates.add(path);
+ }
+
+ public void removeLabelUpdate(TreePath path) {
+ fLabelUpdates.remove(path);
+ }
+
+ public void addStateUpdate(TreePath path) {
+ fStateUpdates.add(path);
+ }
+
+ public void removeStateUpdate(TreePath path) {
+ fStateUpdates.remove(path);
+ }
+
+
+ public boolean isFinished() {
+ return isFinished(ALL_UPDATES_COMPLETE);
+ }
+
+ public boolean isTimedOut() {
+ return fTimeoutInterval > 0 && fTimeoutTime < System.currentTimeMillis();
+ }
+
+ public void waitTillFinished(int flags) throws InterruptedException {
+ synchronized(this) {
+ while(!isFinished(flags)) {
+ wait(100);
+ }
+ }
+ }
+
+ public boolean isFinished(int flags) {
+// if (isTimedOut()) {
+// throw new RuntimeException("Timed Out: " + toString(flags));
+// }
+
+ if (fFailOnRedundantUpdates && !fRedundantUpdates.isEmpty()) {
+ Assert.fail("Redundant Updates: " + fRedundantUpdates.toString());
+ }
+ if (fFailOnMultipleLabelUpdateSequences && !fMultipleLabelUpdateSequencesObserved) {
+ Assert.fail("Multiple label update sequences detected");
+ }
+ if (fFailOnMultipleModelUpdateSequences && fMultipleModelUpdateSequencesObserved) {
+ Assert.fail("Multiple viewer update sequences detected");
+ }
+
+ if ( (flags & LABEL_SEQUENCE_COMPLETE) != 0) {
+ if (!fLabelSequenceComplete) return false;
+ }
+ if ( (flags & LABEL_SEQUENCE_STARTED) != 0) {
+ if (!fLabelUpdatesStarted) return false;
+ }
+ if ( (flags & LABEL_UPDATES) != 0) {
+ if (!fLabelUpdates.isEmpty()) return false;
+ }
+ if ( (flags & CONTENT_SEQUENCE_STARTED) != 0) {
+ if (!fContentSequenceStarted) return false;
+ }
+ if ( (flags & CONTENT_SEQUENCE_COMPLETE) != 0) {
+ if (!fContentSequenceComplete) return false;
+ }
+ if ( (flags & HAS_CHILDREN_UPDATES_STARTED) != 0) {
+ if (fHasChildrenUpdatesRunning.isEmpty() && fHasChildrenUpdatesCompleted.isEmpty()) return false;
+ }
+ if ( (flags & HAS_CHILDREN_UPDATES) != 0) {
+ if (!fHasChildrenUpdatesScheduled.isEmpty()) return false;
+ }
+ if ( (flags & CHILD_COUNT_UPDATES_STARTED) != 0) {
+ if (fChildCountUpdatesRunning.isEmpty() && fChildCountUpdatesCompleted.isEmpty()) return false;
+ }
+ if ( (flags & CHILD_COUNT_UPDATES) != 0) {
+ if (!fChildCountUpdatesScheduled.isEmpty()) return false;
+ }
+ if ( (flags & CHILDREN_UPDATES_STARTED) != 0) {
+ if (fChildrenUpdatesRunning.isEmpty() && fChildrenUpdatesCompleted.isEmpty()) return false;
+ }
+ if ( (flags & CHILDREN_UPDATES) != 0) {
+ if (!fChildrenUpdatesScheduled.isEmpty()) return false;
+ }
+ if ( (flags & MODEL_CHANGED_COMPLETE) != 0) {
+ if (!fModelChangedComplete) return false;
+ }
+ if ( (flags & STATE_SAVE_COMPLETE) != 0) {
+ if (!fStateSaveComplete) return false;
+ }
+ if ( (flags & STATE_SAVE_STARTED) != 0) {
+ if (!fStateSaveStarted) return false;
+ }
+ if ( (flags & STATE_RESTORE_COMPLETE) != 0) {
+ if (!fStateRestoreComplete) return false;
+ }
+ if ( (flags & STATE_RESTORE_STARTED) != 0) {
+ if (!fStateRestoreStarted) return false;
+ }
+ if ( (flags & MODEL_PROXIES_INSTALLED) != 0) {
+ if (!fModelProxyInstalled) return false;
+ }
+ if ( (flags & VIEWER_UPDATES_RUNNING) != 0) {
+ if (fContentUpdatesCounter != 0) {
+ return false;
+ }
+ }
+ if ( (flags & LABEL_UPDATES_RUNNING) != 0) {
+ if (fLabelUpdatesCounter != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public synchronized void updateStarted(IViewerUpdate update) {
+ synchronized (this) {
+ fContentUpdatesCounter++;
+ if (update instanceof IHasChildrenUpdate) {
+ fHasChildrenUpdatesRunning.add(update);
+ } if (update instanceof IChildrenCountUpdate) {
+ fChildCountUpdatesRunning.add(update);
+ } else if (update instanceof IChildrenUpdate) {
+ fChildCountUpdatesRunning.add(update);
+ }
+ }
+ notifyAll();
+ }
+
+ public synchronized void updateComplete(IViewerUpdate update) {
+ synchronized (this) {
+ fContentUpdatesCounter--;
+ }
+
+ if (!update.isCanceled()) {
+ if (update instanceof IHasChildrenUpdate) {
+ fHasChildrenUpdatesRunning.remove(update);
+ fHasChildrenUpdatesCompleted.add(update);
+ if (!fHasChildrenUpdatesScheduled.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
+ fRedundantUpdates.add(update);
+ }
+ } if (update instanceof IChildrenCountUpdate) {
+ fChildCountUpdatesRunning.remove(update);
+ fChildCountUpdatesCompleted.add(update);
+ if (!fChildCountUpdatesScheduled.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
+ fRedundantUpdates.add(update);
+ }
+ } else if (update instanceof IChildrenUpdate) {
+ fChildrenUpdatesRunning.remove(update);
+ fChildrenUpdatesCompleted.add(update);
+
+ int start = ((IChildrenUpdate)update).getOffset();
+ int end = start + ((IChildrenUpdate)update).getLength();
+
+ Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(update.getElementPath());
+ if (childrenIndexes != null) {
+ for (int i = start; i < end; i++) {
+ childrenIndexes.remove(new Integer(i));
+ }
+ if (childrenIndexes.isEmpty()) {
+ fChildrenUpdatesScheduled.remove(update.getElementPath());
+ }
+ } else if (fFailOnRedundantUpdates) {
+ fRedundantUpdates.add(update);
+ }
+ }
+ }
+ notifyAll();
+ }
+
+ public synchronized void viewerUpdatesBegin() {
+ if (fDelayContentUntilProxyInstall && !fModelProxyInstalled) return;
+ if (fFailOnMultipleModelUpdateSequences && fContentSequenceComplete) {
+ fMultipleModelUpdateSequencesObserved = true;
+ }
+ fContentSequenceStarted = true;
+ notifyAll();
+ }
+
+ public synchronized void viewerUpdatesComplete() {
+ if (fDelayContentUntilProxyInstall && !fModelProxyInstalled) return;
+ fContentSequenceComplete = true;
+ notifyAll();
+ }
+
+ public synchronized void labelUpdateComplete(ILabelUpdate update) {
+ fLabelUpdatesRunning.remove(update);
+ fLabelUpdatesCompleted.add(update);
+ fLabelUpdatesCounter--;
+ if (!fLabelUpdates.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
+ fRedundantUpdates.add(update);
+ }
+ notifyAll();
+ }
+
+ public synchronized void labelUpdateStarted(ILabelUpdate update) {
+ fLabelUpdatesRunning.add(update);
+ fLabelUpdatesCounter++;
+ notifyAll();
+ }
+
+ public synchronized void labelUpdatesBegin() {
+ if (fFailOnMultipleLabelUpdateSequences && fLabelSequenceComplete) {
+ fMultipleLabelUpdateSequencesObserved = true;
+ }
+ fLabelUpdatesStarted = true;
+ notifyAll();
+ }
+
+ public synchronized void labelUpdatesComplete() {
+ fLabelSequenceComplete = true;
+ notifyAll();
+ }
+
+ public synchronized void modelChanged(IModelDelta delta, IModelProxy proxy) {
+ fModelChangedComplete = true;
+
+ if (!fModelProxyInstalled) {
+ delta.accept(new IModelDeltaVisitor() {
+ public boolean visit(IModelDelta delta, int depth) {
+ if ((delta.getFlags() & IModelDelta.EXPAND) != 0) {
+ fModelProxyInstalled = true;
+ return false;
+ }
+ return true;
+ }
+ });
+ }
+ notifyAll();
+ }
+
+ public synchronized void stateRestoreUpdatesBegin(Object input) {
+ fStateRestoreStarted = true;
+ notifyAll();
+ }
+
+ public synchronized void stateRestoreUpdatesComplete(Object input) {
+ fStateRestoreComplete = true;
+ notifyAll();
+ }
+
+ public synchronized void stateSaveUpdatesBegin(Object input) {
+ fStateSaveStarted = true;
+ notifyAll();
+ }
+
+ public synchronized void stateSaveUpdatesComplete(Object input) {
+ fStateSaveComplete = true;
+ notifyAll();
+ }
+
+ public void stateUpdateComplete(Object input, IViewerUpdate update) {
+ }
+
+ public void stateUpdateStarted(Object input, IViewerUpdate update) {
+ }
+
+ private String toString(int flags) {
+ StringBuffer buf = new StringBuffer("Viewer Update Listener");
+
+ if (fFailOnRedundantUpdates) {
+ buf.append("\n\t");
+ buf.append("fRedundantUpdates = ");
+ buf.append( toStringViewerUpdatesSet(fRedundantUpdates) );
+ }
+ if (fFailOnMultipleLabelUpdateSequences) {
+ buf.append("\n\t");
+ buf.append("fMultipleLabelUpdateSequencesObserved = " + fMultipleLabelUpdateSequencesObserved);
+ }
+ if (fFailOnMultipleModelUpdateSequences) {
+ buf.append("\n\t");
+ buf.append("fMultipleModelUpdateSequencesObserved = " + fMultipleModelUpdateSequencesObserved);
+ }
+ if ( (flags & LABEL_SEQUENCE_COMPLETE) != 0) {
+ buf.append("\n\t");
+ buf.append("fLabelSequenceComplete = " + fLabelSequenceComplete);
+ }
+ if ( (flags & LABEL_UPDATES_RUNNING) != 0) {
+ buf.append("\n\t");
+ buf.append("fLabelUpdatesRunning = " + fLabelUpdatesCounter);
+ }
+ if ( (flags & LABEL_SEQUENCE_STARTED) != 0) {
+ buf.append("\n\t");
+ buf.append("fLabelUpdatesRunning = ");
+ buf.append( toStringViewerUpdatesSet(fLabelUpdatesRunning) );
+ buf.append("\n\t");
+ buf.append("fLabelUpdatesCompleted = ");
+ buf.append( toStringViewerUpdatesSet(fLabelUpdatesCompleted) );
+ }
+ if ( (flags & LABEL_UPDATES) != 0) {
+ buf.append("\n\t");
+ buf.append("fLabelUpdates = ");
+ buf.append( toString(fLabelUpdates) );
+ }
+ if ( (flags & CONTENT_SEQUENCE_COMPLETE) != 0) {
+ buf.append("\n\t");
+ buf.append("fContentSequenceComplete = " + fContentSequenceComplete);
+ }
+ if ( (flags & VIEWER_UPDATES_RUNNING) != 0) {
+ buf.append("\n\t");
+ buf.append("fContentUpdatesCounter = " + fContentUpdatesCounter);
+ }
+ if ( (flags & HAS_CHILDREN_UPDATES_STARTED) != 0) {
+ buf.append("\n\t");
+ buf.append("fHasChildrenUpdatesRunning = ");
+ buf.append( toStringViewerUpdatesSet(fHasChildrenUpdatesRunning) );
+ buf.append("\n\t");
+ buf.append("fHasChildrenUpdatesCompleted = ");
+ buf.append( toStringViewerUpdatesSet(fHasChildrenUpdatesCompleted) );
+ }
+ if ( (flags & HAS_CHILDREN_UPDATES) != 0) {
+ buf.append("\n\t");
+ buf.append("fHasChildrenUpdates = ");
+ buf.append( toString(fHasChildrenUpdatesScheduled) );
+ }
+ if ( (flags & CHILD_COUNT_UPDATES_STARTED) != 0) {
+ buf.append("\n\t");
+ buf.append("fChildCountUpdatesRunning = ");
+ buf.append( toStringViewerUpdatesSet(fChildCountUpdatesRunning) );
+ buf.append("\n\t");
+ buf.append("fChildCountUpdatesCompleted = ");
+ buf.append( toStringViewerUpdatesSet(fChildCountUpdatesCompleted) );
+ }
+ if ( (flags & CHILD_COUNT_UPDATES) != 0) {
+ buf.append("\n\t");
+ buf.append("fChildCountUpdates = ");
+ buf.append( toString(fChildCountUpdatesScheduled) );
+ }
+ if ( (flags & CHILDREN_UPDATES_STARTED) != 0) {
+ buf.append("\n\t");
+ buf.append("fChildrenUpdatesRunning = ");
+ buf.append( fChildrenUpdatesRunning );
+ buf.append("\n\t");
+ buf.append("fChildrenUpdatesCompleted = ");
+ buf.append( toStringViewerUpdatesSet(fChildrenUpdatesCompleted) );
+ }
+ if ( (flags & CHILDREN_UPDATES) != 0) {
+ buf.append("\n\t");
+ buf.append("fChildrenUpdates = ");
+ buf.append( toStringTreePathMap(fChildrenUpdatesScheduled) );
+ }
+ if ( (flags & MODEL_CHANGED_COMPLETE) != 0) {
+ buf.append("\n\t");
+ buf.append("fModelChangedComplete = " + fModelChangedComplete);
+ }
+ if ( (flags & STATE_SAVE_COMPLETE) != 0) {
+ buf.append("\n\t");
+ buf.append("fStateSaveComplete = " + fStateSaveComplete);
+ }
+ if ( (flags & STATE_RESTORE_COMPLETE) != 0) {
+ buf.append("\n\t");
+ buf.append("fStateRestoreComplete = " + fStateRestoreComplete);
+ }
+ if ( (flags & MODEL_PROXIES_INSTALLED) != 0) {
+ buf.append("\n\t");
+ buf.append("fModelProxyInstalled = " + fModelProxyInstalled);
+ }
+ if (fTimeoutInterval > 0) {
+ buf.append("\n\t");
+ buf.append("fTimeoutInterval = " + fTimeoutInterval);
+ }
+ return buf.toString();
+ }
+
+ private String toString(Set<TreePath> set) {
+ if (set.isEmpty()) {
+ return "(EMPTY)";
+ }
+ StringBuffer buf = new StringBuffer();
+ for (Iterator<TreePath> itr = set.iterator(); itr.hasNext(); ) {
+ buf.append("\n\t\t");
+ buf.append(toStringTreePath(itr.next()));
+ }
+ return buf.toString();
+ }
+
+ private String toStringViewerUpdatesSet(Set<IViewerUpdate> set) {
+ if (set.isEmpty()) {
+ return "(EMPTY)";
+ }
+ StringBuffer buf = new StringBuffer();
+ for (Iterator<IViewerUpdate> itr = set.iterator(); itr.hasNext(); ) {
+ buf.append("\n\t\t");
+ buf.append(toStringTreePath((itr.next()).getElementPath()));
+ }
+ return buf.toString();
+ }
+
+ private String toStringTreePathMap(Map<TreePath, Set<Integer>> map) {
+ if (map.isEmpty()) {
+ return "(EMPTY)";
+ }
+ StringBuffer buf = new StringBuffer();
+ for (Iterator<TreePath> itr = map.keySet().iterator(); itr.hasNext(); ) {
+ buf.append("\n\t\t");
+ TreePath path = itr.next();
+ buf.append(toStringTreePath(path));
+ Set<?> updates = map.get(path);
+ buf.append(" = ");
+ buf.append(updates.toString());
+ }
+ return buf.toString();
+ }
+
+ private String toStringTreePath(TreePath path) {
+ if (path.getSegmentCount() == 0) {
+ return "/";
+ }
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < path.getSegmentCount(); i++) {
+ buf.append("/");
+ buf.append(path.getSegment(i));
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString(ALL_UPDATES_COMPLETE | MODEL_CHANGED_COMPLETE | STATE_RESTORE_COMPLETE |
+ VIEWER_UPDATES_STARTED | LABEL_SEQUENCE_STARTED);
+ }
+}
+
+
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java
new file mode 100644
index 000000000..a06fd6789
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/VirtualViewerUpdatesListener.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test;
+
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer;
+
+/**
+ * Extends base listener to use virtual viewer capabilities.
+ */
+public class VirtualViewerUpdatesListener extends ViewerUpdatesListener {
+ private final VirtualTreeModelViewer fVirtualViewer;
+
+ public VirtualViewerUpdatesListener(VirtualTreeModelViewer viewer) {
+ super(viewer, false, false);
+ fVirtualViewer = viewer;
+ }
+
+ public VirtualItem findElement(Pattern[] patterns) {
+ return findElement(fVirtualViewer.getTree(), patterns);
+ }
+
+ public VirtualItem findElement(VirtualItem parent, Pattern[] patterns) {
+ VirtualItem item = parent;
+ patterns: for (int i = 0; i < patterns.length; i++) {
+ for (VirtualItem child : item.getItems()) {
+ String[] label = (String[])child.getData(VirtualItem.LABEL_KEY);
+ if (label != null && label.length >= 1 && label[0] != null && patterns[i].matcher(label[0]).matches()) {
+ item = child;
+ continue patterns;
+ }
+ }
+ return null;
+ }
+ return item;
+ }
+
+
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java
new file mode 100644
index 000000000..1e3281d1a
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateCallback.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test.util;
+
+import java.util.List;
+
+/**
+ * Copied and adapted from org.eclipse.cdt.dsf.concurrent.
+ *
+ * A request monitor that is used for multiple activities. We are told the
+ * number of activities that constitutes completion. When our {@link #done()} is
+ * called that many times, the request is considered complete.
+ *
+ * The usage is as follows: <code><pre>
+ * final CountingRequestMonitor countingRm = new CountingRequestMonitor(fExecutor, null) {
+ * public void handleCompleted() {
+ * System.out.println("All complete, errors=" + !getStatus().isOK());
+ * }
+ * };
+ *
+ * int count = 0;
+ * for (int i : elements) {
+ * service.call(i, countingRm);
+ * count++;
+ * }
+ *
+ * countingRm.setDoneCount(count);
+ * </pre></code>
+ *
+ * @since 1.0
+ */
+public class AggregateCallback extends Callback {
+
+ /**
+ * Counter tracking the remaining number of times that the done() method
+ * needs to be called before this request monitor is actually done.
+ */
+ private int fDoneCounter;
+
+ /**
+ * Flag indicating whether the initial count has been set on this monitor.
+ */
+ private boolean fInitialCountSet = false;
+
+ public AggregateCallback(Callback parentCallback) {
+ super(parentCallback);
+ }
+
+ /**
+ * Sets the number of times that this request monitor needs to be called
+ * before this monitor is truly considered done. This method must be called
+ * exactly once in the life cycle of each counting request monitor.
+ * @param count Number of times that done() has to be called to mark the request
+ * monitor as complete. If count is '0', then the counting request monitor is
+ * marked as done immediately.
+ */
+ public synchronized void setDoneCount(int count) {
+ assert !fInitialCountSet;
+ fInitialCountSet = true;
+ fDoneCounter += count;
+ if (fDoneCounter <= 0) {
+ assert fDoneCounter == 0; // Mismatch in the count.
+ super.done();
+ }
+ }
+
+ /**
+ * Called to indicate that one of the calls using this monitor is finished.
+ * Only when we've been called the number of times corresponding to the
+ * completion count will this request monitor will be considered complete.
+ * This method can be called before {@link #setDoneCount(int)}; in that
+ * case, we simply bump the count that tracks the number of times we've been
+ * called. The monitor will not move into the completed state until after
+ * {@link #setDoneCount(int)} has been called (or during if the given
+ * completion count is equal to the number of times {@link #done()} has
+ * already been called.)
+ */
+ @Override
+ public synchronized void done() {
+ fDoneCounter--;
+ if (fInitialCountSet && fDoneCounter <= 0) {
+ assert fDoneCounter == 0; // Mismatch in the count.
+ super.done();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "AggregateError: " + getError().toString(); //$NON-NLS-1$
+ }
+
+ @Override
+ public synchronized void setError(Throwable error) {
+ if ((getError() == null)) {
+ setError(new AggregateError("") {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String getMessage() {
+ StringBuffer message = new StringBuffer();
+ List<Throwable> children = getChildren();
+ for (int i = 0; i < children.size(); i++) {
+ message.append(children.get(i).getMessage());
+ if (i + 1 < children.size()) {
+ message.append(" ,"); //$NON-NLS-1$
+ }
+ }
+ return message.toString();
+ }
+ });
+ }
+
+
+ if ((getError() instanceof AggregateError)) {
+ ((AggregateError)getError()).add(error);
+ }
+ };
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java
new file mode 100644
index 000000000..a358f8523
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/AggregateError.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Exception that combines several exceptions for propagation to client.
+ */
+public class AggregateError extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ final private List<Throwable> fChildren = Collections.synchronizedList(new ArrayList<Throwable>(1));
+
+ public AggregateError(String message) {
+ super(message);
+ }
+
+ public void add(Throwable child) {
+ boolean initCause = false;
+ synchronized(fChildren) {
+ if (fChildren.isEmpty()) {
+ initCause = true;
+ }
+ fChildren.add(child);
+ }
+ if (initCause) {
+ super.initCause(child);
+ }
+ }
+
+ public List<Throwable> getChildren() {
+ return fChildren;
+ }
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java
new file mode 100644
index 000000000..78c7e47bc
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java
@@ -0,0 +1,424 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+
+import org.eclipse.tcf.protocol.Protocol;
+
+/**
+ * Copied and apdapted from org.eclipse.cdt.dsf.concurrent.
+ *
+ * Used to monitor the result of an asynchronous request. Because of the
+ * asynchronous nature of DSF code, a very large number of methods needs to
+ * signal the result of an operation through a call-back. This class is the base
+ * class for such call backs.
+ * <p>
+ * The intended use of this class, is that a client who is calling an asynchronous
+ * method, will sub-class Callback, and implement the method {@link #handleCompleted()},
+ * or any of the other <code>handle...</code> methods, in order to interpret the
+ * results of the request. The object implementing the asynchronous method is required
+ * to call the {@link #done()} method on the request monitor object that it received
+ * as an argument.
+ * </p>
+ * <p>
+ * The error the returned by #getError() can be used to
+ * determine the success or failure of the asynchronous operation. By convention
+ * the error value returned by asynchronous method should be interpreted as follows:
+ * <ul>
+ * <li><code>null</code> - Result is a success. In DataCallback, getData() should
+ * return a value.</li>
+ * <li>non-<code>null</code> - An error condition that should probably be reported
+ * to the user.</li>
+ * <li><code>CancellationException</code> - The request was canceled, and the
+ * asynchronous method was not completed.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The Callback constructor accepts an optional "parent" request monitor. If a
+ * parent monitor is specified, it will automatically be invoked by this monitor when
+ * the request is completed. The parent option is useful when implementing a method
+ * which is asynchronous (and accepts a request monitor as an argument) and which itself
+ * calls another asynchronous method to complete its operation. For example, in the
+ * request monitor implementation below, the implementation only needs to override
+ * <code>handleSuccess()</code>, because the base implementation will handle notifying the
+ * parent <code>rm</code> in case the <code>getIngredients()</code> call fails.
+ * <pre>
+ * public void createCupCakes(final DataCallback<CupCake[]> rm) {
+ * getIngredients(new DataCallback<Ingredients>(fExecutor, rm) {
+ * public void handleSuccess() {
+ * rm.setData( new CupCake(getData().getFlour(), getData().getSugar(),
+ * getData().getBakingPowder()));
+ * rm.done();
+ * }
+ * });
+ * }
+ * </pre>
+ * </p>
+ *
+ * @since 1.0
+ */
+public class Callback {
+
+ /**
+ * Interface used by Callback to notify when a given request monitor
+ * is canceled.
+ *
+ * @see Callback
+ */
+ public static interface ICanceledListener {
+
+ /**
+ * Called when the given request monitor is canceled.
+ */
+ public void requestCanceled(Callback rm);
+ }
+
+ /**
+ * The request monitor which was used to call into the method that created this
+ * monitor.
+ */
+ private final Callback fParentCallback;
+
+ private List<ICanceledListener> fCancelListeners;
+
+ /**
+ * Status
+ */
+ private Throwable fError = null;
+ private boolean fCanceled = false;
+ private boolean fDone = false;
+
+ private final ICanceledListener fCanceledListener;
+
+ /**
+ * This field is never read by any code; its purpose is strictly to assist
+ * developers debug DPF code. Developer can select this field in the
+ * Variables view and see a monitor backtrace in the details pane. See
+ * {@link DsfExecutable#DEBUG_MONITORS}.
+ *
+ * <p>
+ * This field is set only when tracing is enabled.
+ */
+ @SuppressWarnings("unused")
+ private String fMonitorBacktrace;
+
+ public Callback() {
+ this(null);
+ }
+
+ /**
+ * Constructor with an optional parent monitor.
+ *
+ * @param executor
+ * This executor will be used to invoke the runnable that will
+ * allow processing the completion code of this request monitor.
+ * I.e., the runnable will call {@link #handleCompleted()}.
+ * @param parentCallback
+ * An optional parent request monitor. By default, our completion
+ * handlers invoke the parent monitor's <code>done</code> method,
+ * thus allowing monitors to be daisy chained. If this request is
+ * unsuccessful, its status is set into the parent monitor.
+ * Parameter may be null.
+ */
+ public Callback(Callback parentCallback) {
+ fParentCallback = parentCallback;
+
+ // If the parent rm is not null, add ourselves as a listener so that
+ // this request monitor will automatically be canceled when the parent
+ // is canceled.
+ if (fParentCallback != null) {
+ fCanceledListener = new ICanceledListener() {
+ public void requestCanceled(Callback rm) {
+ cancel();
+ }
+ };
+
+ fParentCallback.addCancelListener(fCanceledListener);
+ } else {
+ fCanceledListener = null;
+ }
+ }
+
+ /**
+ * Sets the status of the result of the request. If status is OK, this
+ * method does not need to be called.
+ */
+ public synchronized void setError(Throwable error) {
+ fError = error;
+ }
+
+ /** Returns the status of the completed method. */
+ public synchronized Throwable getError() {
+ if (isCanceled()) {
+ return new CancellationException();
+ }
+ return fError;
+ }
+
+ /**
+ * Sets this request monitor as canceled and calls the cancel listeners if
+ * any.
+ * <p>
+ * Note: Calling cancel() does not automatically complete the
+ * Callback. The asynchronous call still has to call done().
+ * </p>
+ * <p>
+ * Note: logically a request should only be canceled by the client that
+ * issued the request in the first place. After a request is canceled, the
+ * method that is fulfilling the request may call
+ * {@link #setError(Throwable)} with <code>CancelledException</code>
+ * to indicate that it recognized that the given request was canceled and it
+ * did not perform the given operation.
+ * </p>
+ * <p>
+ * Canceling a monitor effectively cancels all descendant monitors, by
+ * virtue of the default implementation of {@link #isCanceled()}, which
+ * checks not only its own state but that of its parent. However, only the
+ * cancel listeners of the monitor directly canceled will be called.
+ * </p>
+ */
+ public void cancel() {
+ ICanceledListener[] listeners = null;
+ synchronized (this) {
+ // Check to make sure the request monitor wasn't previously canceled.
+ if (!fCanceled) {
+ fCanceled = true;
+ if (fCancelListeners != null) {
+ listeners = fCancelListeners.toArray(new ICanceledListener[fCancelListeners.size()]);
+ }
+ }
+ }
+
+ // Call the listeners outside of a synchronized section to reduce the
+ // risk of deadlocks.
+ if (listeners != null) {
+ for (ICanceledListener listener : listeners) {
+ listener.requestCanceled(this);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the request was canceled. Even if the request is
+ * canceled by the client, the implementor handling the request should
+ * still call {@link #done()} in order to complete handling
+ * of the request monitor.
+ *
+ * <p>
+ * A request monitor is considered canceled if either it or its parent was canceled.
+ * </p>
+ */
+ public boolean isCanceled() {
+ boolean canceled = false;
+
+ // Avoid holding onto this lock while calling parent RM, which may
+ // acquire other locks (bug 329488).
+ synchronized(this) {
+ canceled = fCanceled;
+ }
+ return canceled || (fParentCallback != null && fParentCallback.isCanceled());
+ }
+
+ /**
+ * Adds the given listener to list of listeners that are notified when this
+ * request monitor is directly canceled.
+ */
+ public synchronized void addCancelListener(ICanceledListener listener) {
+ if (fCancelListeners == null) {
+ fCancelListeners = new ArrayList<ICanceledListener>(1);
+ }
+ fCancelListeners.add(listener);
+ }
+
+ /**
+ * Removes the given listener from the list of listeners that are notified
+ * when this request monitor is directly canceled.
+ */
+ public synchronized void removeCancelListener(ICanceledListener listener) {
+ if (fCancelListeners != null) {
+ fCancelListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Marks this request as completed. Once this method is called, the
+ * monitor submits a runnable to the DSF Executor to call the
+ * <code>handle...</code> methods.
+ * <p>
+ * Note: This method should be called once and only once, for every request
+ * issued. Even if the request was canceled.
+ * </p>
+ */
+ public synchronized void done() {
+ if (fDone) {
+ throw new IllegalStateException("Callback: " + this + ", done() method called more than once"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ fDone = true;
+
+ // This Callback is done, it can no longer be canceled.
+ // We must clear the list of cancelListeners because it causes a
+ // circular reference between parent and child Callback, which
+ // causes a memory leak.
+ fCancelListeners = null;
+
+ if (fParentCallback != null) {
+ fParentCallback.removeCancelListener(fCanceledListener);
+ }
+
+ try {
+ Protocol.invokeLater(new Runnable() {
+ public void run() {
+ Callback.this.handleCompleted();
+ }
+ @Override
+ public String toString() {
+ return "Completed: " + Callback.this.toString(); //$NON-NLS-1$
+ }
+ });
+ } catch (IllegalStateException e) {
+ handleEventQueueShutDown(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Callback (" + super.toString() + "): " + getError(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Checks whether the given request monitor completed with success or
+ * failure result. If the request monitor was canceled it is considered a
+ * failure, regardless of the status. If the status has a severity higher
+ * than INFO (i.e., WARNING, ERROR or CANCEL), it is considered a failure.
+ */
+ public synchronized boolean isSuccess() {
+ return !isCanceled() && getError() == null ;
+ }
+
+ /**
+ * First tier handler for the completion of the request. By default, the
+ * {@link #done()} method drives this method on the executor specified at
+ * construction time. By default, this handler merely calls a more
+ * specialized handler, which in turn may call an even more specialized
+ * handler, and so on, thus giving a subclass the ability to
+ * compartmentalize its completion logic by overriding specific handlers.
+ * All handlers are named <code>handleXxxxx</code>. More specifically, the
+ * base implementation calls {@link #handleSuccess()} if the request
+ * succeeded, and calls {@link #handleFailure()} otherwise. <br>
+ *
+ * The complete hierarchy of handlers is as follows: <br>
+ * <pre>
+ * + handleCompleted
+ * - handleSuccess
+ * + handleFailure
+ * - handleCancel
+ * + handleErrororWarning
+ * - handleError
+ * - handleWarning
+ * </pre>
+ *
+ * <p>
+ * Note: Sub-classes may override this method.
+ */
+ protected void handleCompleted() {
+ if (isSuccess()) {
+ handleSuccess();
+ } else {
+ handleFailure();
+ }
+ }
+
+ /**
+ * Default handler for a successful the completion of a request. If this
+ * monitor has a parent monitor that was configured by the constructor, that
+ * parent monitor is notified. Otherwise this method does nothing.
+ * {@link #handleFailure()} or cancel otherwise.
+ * <br>
+ * Note: Sub-classes may override this method.
+ */
+ protected void handleSuccess() {
+ if (fParentCallback != null) {
+ fParentCallback.done();
+ }
+ }
+
+ /**
+ * The default implementation of a cancellation or an error result of a
+ * request. The implementation delegates to {@link #handleCancel()} and
+ * {@link #handleErrorOrWarning()} as needed.
+ * <br>
+ * Note: Sub-classes may override this method.
+ */
+ protected void handleFailure() {
+ assert !isSuccess();
+
+ if (isCanceled()) {
+ handleCancel();
+ } else {
+ handleError();
+ }
+ }
+
+ /**
+ * The default implementation of an error result of a request. If this
+ * monitor has a parent monitor that was configured by the constructor, that
+ * parent monitor is configured with a new status containing this error.
+ * Otherwise the error is logged.
+ * <br>
+ * Note: Sub-classes may override this method.
+ */
+ protected void handleError() {
+ if (fParentCallback != null) {
+ fParentCallback.setError(getError());
+ fParentCallback.done();
+ } else {
+ Protocol.log("Unhandled error in callback " + toString(), getError());
+ }
+ }
+
+
+ /**
+ * Default completion handler for a canceled request. If this monitor was
+ * constructed with a parent monitor, the status is propagated up to it.
+ * Otherwise this method does nothing. <br>
+ * Note: Sub-classes may override this method.
+ */
+ protected void handleCancel() {
+ if (fParentCallback != null) {
+ if (getError() instanceof CancellationException && !fParentCallback.isCanceled()) {
+ Protocol.log("Sub-request " + toString() + " was canceled and not handled.'", getError());
+ } else {
+ fParentCallback.setError(getError());
+ }
+ fParentCallback.done();
+ }
+ }
+
+ /**
+ * Default handler for when the executor supplied in the constructor
+ * rejects the runnable that is submitted invoke this request monitor.
+ * This usually happens only when the executor is shutting down.
+ * <p>
+ * The default handler creates a new error status for the rejected
+ * execution and propagates it to the client or logs it.
+ */
+ protected void handleEventQueueShutDown(IllegalStateException e) {
+ if (fParentCallback != null) {
+ fParentCallback.setError(e);
+ fParentCallback.done();
+ } else {
+ Protocol.log("In callback " + toString() + ", unhandled event queue shut down.", e);
+ }
+ }
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java
new file mode 100644
index 000000000..e9822fa16
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/DataCallback.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2009 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test.util;
+
+
+
+/**
+ * Copied and apdapted from org.eclipse.cdt.dsf.concurrent.
+ *
+ * Request monitor that allows data to be returned to the request initiator.
+ *
+ * @param V The type of the data object that this monitor handles.
+ *
+ * @since 1.0
+ */
+public class DataCallback<V> extends Callback {
+
+ /** Data object reference */
+ private V fData;
+
+ public DataCallback() {
+ this(null);
+ }
+
+ public DataCallback(Callback parentCallback) {
+ super(parentCallback);
+ }
+
+ /**
+ * Sets the data object to specified value. To be called by the
+ * asynchronous method implementor.
+ * @param data Data value to set.
+ */
+ public synchronized void setData(V data) { fData = data; }
+
+ /**
+ * Returns the data value, null if not set.
+ */
+ public synchronized V getData() { return fData; }
+
+ @Override
+ public String toString() {
+ if (getData() != null) {
+ return getData().toString();
+ } else {
+ return super.toString();
+ }
+ }
+}
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java
new file mode 100644
index 000000000..b75b74627
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Query.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test.util;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.tcf.protocol.Protocol;
+
+
+/**
+ * Copied and adapted from org.eclipse.cdt.dsf.concurrent.
+ *
+ * A convenience class that allows a client to retrieve data from services
+ * synchronously from a non-dispatch thread. This class is different from
+ * a Callable<V> in that it allows the implementation code to calculate
+ * the result in several dispatches, rather than requiring it to return the
+ * data at end of Callable#call method.
+ * <p>
+ * Usage:<br/>
+ * <pre>
+ * class DataQuery extends Query<Data> {
+ * protected void execute(DataCallback<Data> callback) {
+ * callback.setData(fSlowService.getData());
+ * callback.done();
+ * }
+ * }
+ *
+ * DsfExecutor executor = getExecutor();
+ * DataQuery query = new DataQuery();
+ * executor.submit(query);
+ *
+ * try {
+ * Data data = query.get();
+ * }
+ *
+ * </pre>
+ * <p>
+ * @see java.util.concurrent.Callable
+ *
+ */
+abstract public class Query<V> implements Future<V>
+{
+ private class QueryCallback extends DataCallback<V> {
+
+ boolean fExecuted = false;
+
+ boolean fCompleted = false;
+
+ private QueryCallback() {
+ super(null);
+ }
+
+ @Override
+ public synchronized void handleCompleted() {
+ fCompleted = true;
+ notifyAll();
+ }
+
+ public synchronized boolean isCompleted() {
+ return fCompleted;
+ }
+
+ public synchronized boolean setExecuted() {
+ if (fExecuted || isCanceled()) {
+ // already executed or canceled
+ return false;
+ }
+ fExecuted = true;
+ return true;
+ }
+ };
+
+ private final QueryCallback fCallback = new QueryCallback();
+
+ /**
+ * The no-argument constructor
+ */
+ public Query() {}
+
+ public V get() throws InterruptedException, ExecutionException {
+ invoke();
+ Throwable error;
+ V data;
+ synchronized (fCallback) {
+ while (!isDone()) {
+ fCallback.wait();
+ }
+ error = fCallback.getError();
+ data = fCallback.getData();
+ }
+
+ if (error instanceof CancellationException) {
+ throw new CancellationException();
+ } else if (error != null) {
+ throw new ExecutionException(error);
+ }
+ return data;
+ }
+
+ public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ invoke();
+
+ long timeLeft = unit.toMillis(timeout);
+ long timeoutTime = System.currentTimeMillis() + unit.toMillis(timeout);
+
+ Throwable error;
+ V data;
+ synchronized (fCallback) {
+ while (!isDone()) {
+ if (timeLeft <= 0) {
+ throw new TimeoutException();
+ }
+ fCallback.wait(timeLeft);
+ timeLeft = timeoutTime - System.currentTimeMillis();
+ }
+ error = fCallback.getError();
+ data = fCallback.getData();
+ }
+
+ if (error instanceof CancellationException) {
+ throw new CancellationException();
+ } else if (error != null) {
+ throw new ExecutionException(error);
+ }
+ return data;
+ }
+
+ /**
+ * Don't try to interrupt the DSF executor thread, just ignore the request
+ * if set.
+ */
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ boolean completed = false;
+ synchronized (fCallback) {
+ completed = fCallback.isCompleted();
+ if (!completed) {
+ fCallback.cancel();
+ fCallback.notifyAll();
+ }
+ }
+ return !completed;
+ }
+
+ public boolean isCancelled() { return fCallback.isCanceled(); }
+
+ public boolean isDone() {
+ synchronized (fCallback) {
+ // If future is canceled, return right away.
+ return fCallback.isCompleted() || fCallback.isCanceled();
+ }
+ }
+
+ abstract protected void execute(DataCallback<V> callback);
+
+ public void invoke() {
+ Protocol.invokeLater(new Runnable() {
+ public void run() {
+ if (fCallback.setExecuted()) {
+ execute(fCallback);
+ }
+ }
+ });
+ }
+}
+
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java
new file mode 100644
index 000000000..8af55fa85
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Task.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2009 Wind River Systems 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:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.debug.test.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.tcf.protocol.Protocol;
+
+/**
+ * Future implementation that executes a call on the TCF Protocol
+ * thread.
+ */
+public abstract class Task<V> implements Future<V> , Callable<V> {
+
+ private final AtomicBoolean fInvoked = new AtomicBoolean(false);
+ private final FutureTask<V> fInternalFT = new FutureTask<V>(this);
+
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return fInternalFT.cancel(mayInterruptIfRunning);
+ }
+
+ public boolean isCancelled() {
+ return fInternalFT.isCancelled();
+ }
+
+ public boolean isDone() {
+ return fInternalFT.isDone();
+ }
+
+ public V get() throws InterruptedException, ExecutionException {
+ invoke();
+ return fInternalFT.get();
+ }
+
+ public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ invoke();
+ return fInternalFT.get(timeout, unit);
+ }
+
+ public void invoke() {
+ if (!fInvoked.getAndSet(true)) {
+ Protocol.invokeLater(fInternalFT);
+ }
+ }
+
+ abstract public V call() throws Exception;
+}

Back to the top