The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at http://www.eclipse.org/legal/epl-v10.html.
+For purposes of the EPL, "Program" will mean the Content.
+
+
If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at http://www.eclipse.org.
+
+
\ No newline at end of file
diff --git a/plugins/org.eclipse.dd.examples.dsf/build.properties b/plugins/org.eclipse.dd.examples.dsf/build.properties
new file mode 100644
index 00000000000..bc3b0f2bf1f
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/build.properties
@@ -0,0 +1,7 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ plugin.properties,\
+ about.html
diff --git a/plugins/org.eclipse.dd.examples.dsf/icons/alarm.gif b/plugins/org.eclipse.dd.examples.dsf/icons/alarm.gif
new file mode 100644
index 00000000000..33cc76e9dc5
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/icons/alarm.gif differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/icons/alarm_triggered.gif b/plugins/org.eclipse.dd.examples.dsf/icons/alarm_triggered.gif
new file mode 100644
index 00000000000..609dbb7269c
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/icons/alarm_triggered.gif differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/icons/layout.gif b/plugins/org.eclipse.dd.examples.dsf/icons/layout.gif
new file mode 100644
index 00000000000..4a07fffc16a
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/icons/layout.gif differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/icons/remove.gif b/plugins/org.eclipse.dd.examples.dsf/icons/remove.gif
new file mode 100644
index 00000000000..2cd9c544436
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/icons/remove.gif differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/icons/sample.gif b/plugins/org.eclipse.dd.examples.dsf/icons/sample.gif
new file mode 100644
index 00000000000..34fb3c9d8cb
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/icons/sample.gif differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/icons/timer.gif b/plugins/org.eclipse.dd.examples.dsf/icons/timer.gif
new file mode 100644
index 00000000000..6089d528ce0
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/icons/timer.gif differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/plugin.properties b/plugins/org.eclipse.dd.examples.dsf/plugin.properties
new file mode 100644
index 00000000000..36cabf984ee
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/plugin.properties
@@ -0,0 +1,13 @@
+###############################################################################
+# Copyright (c) 2006 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
+###############################################################################
+pluginName=DSDP/DD Debugger Services Framework (DSF) Examples
+providerName=Eclipse.org
+
diff --git a/plugins/org.eclipse.dd.examples.dsf/plugin.xml b/plugins/org.eclipse.dd.examples.dsf/plugin.xml
new file mode 100644
index 00000000000..798b7f3e230
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/plugin.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/DsfExamplesPlugin.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/DsfExamplesPlugin.java
new file mode 100644
index 00000000000..92203c6d2cd
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/DsfExamplesPlugin.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class DsfExamplesPlugin extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.dd.examples.dsf"; //$NON-NLS-1$
+
+ public static final String IMG_LAYOUT_TOGGLE = "icons/layout.gif"; //$NON-NLS-1$
+ public static final String IMG_ALARM = "icons/alarm.gif"; //$NON-NLS-1$
+ public static final String IMG_ALARM_TRIGGERED = "icons/alarm_triggered.gif"; //$NON-NLS-1$
+ public static final String IMG_TIMER = "icons/timer.gif"; //$NON-NLS-1$
+ public static final String IMG_REMOVE = "icons/remove.gif"; //$NON-NLS-1$
+
+ // The shared instance
+ private static DsfExamplesPlugin fgPlugin;
+
+ private static BundleContext fgBundleContext;
+
+ /**
+ * The constructor
+ */
+ public DsfExamplesPlugin() {
+ fgPlugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void start(BundleContext context) throws Exception {
+ fgBundleContext = context;
+ super.start(context);
+ getImageRegistry().put(IMG_ALARM, imageDescriptorFromPlugin(PLUGIN_ID, IMG_ALARM));
+ getImageRegistry().put(IMG_ALARM_TRIGGERED, imageDescriptorFromPlugin(PLUGIN_ID, IMG_ALARM_TRIGGERED));
+ getImageRegistry().put(IMG_TIMER, imageDescriptorFromPlugin(PLUGIN_ID, IMG_TIMER));
+ getImageRegistry().put(IMG_REMOVE, imageDescriptorFromPlugin(PLUGIN_ID, IMG_REMOVE));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ super.stop(context);
+ fgPlugin = null;
+ fgBundleContext = null;
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static DsfExamplesPlugin getDefault() {
+ return fgPlugin;
+ }
+
+ public static BundleContext getBundleContext() {
+ return fgBundleContext;
+ }
+
+ /**
+ * Returns an image descriptor for the image file at the given
+ * plug-in relative path
+ *
+ * @param path the path
+ * @return the image descriptor
+ */
+ public static ImageDescriptor getImageDescriptor(String path) {
+ return imageDescriptorFromPlugin(PLUGIN_ID, path);
+ }
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellableInputCoalescingSlowDataProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellableInputCoalescingSlowDataProvider.java
new file mode 100644
index 00000000000..d9839e94334
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellableInputCoalescingSlowDataProvider.java
@@ -0,0 +1,440 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.dsf.concurrent.DsfExecutor;
+import org.eclipse.dd.dsf.concurrent.DsfRunnable;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
+
+/**
+ * Example data provider which has a built-in delay when fetching data. This
+ * data provider simulates a service which retrieves data from an external
+ * source such as a networked target, which incurs a considerable delay when
+ * retrieving data. The data items are simulated values which consist of the
+ * time when data is being retrieved followed by the item's index.
+ *
+ * This version of the data provider builds on the input-coalescing feature,
+ * it adds a test to check if the requests were cancelled by the user, before
+ * they are coalesced and queued to be processed by the provider thread.
+ */
+public class CancellableInputCoalescingSlowDataProvider implements DataProvider {
+
+ /** Minimum count of data items */
+ private final static int MIN_COUNT = 1000;
+
+ /** Maximum count of data items */
+ private final static int MAX_COUNT = 2000;
+
+ /** Time interval how often random changes occur. */
+ private final static int RANDOM_CHANGE_MILIS = 10000;
+
+ /** Number of times random changes are made, before count is changed. */
+ private final static int RANDOM_COUNT_CHANGE_INTERVALS = 3;
+
+ /** Percentage of values that is changed upon random change (0-100). */
+ private final static int RANDOM_CHANGE_SET_PERCENTAGE = 10;
+
+ /**
+ * Amount of time (in miliseconds) how long the requests to provider, and
+ * events from provider are delayed by.
+ */
+ private final static int TRANSMISSION_DELAY_TIME = 500;
+
+ /**
+ * Amount of time (in milliseconds) how long the provider takes to process
+ * a request.
+ */
+ private final static int PROCESSING_TIME = 100;
+
+ /**
+ * Maximum number of item requests that can be coalesced into a single
+ * request.
+ */
+ private final static int COALESCING_COUNT_LIMIT = 10;
+
+ /**
+ * Delay in processing the buffer of getItem() calls. This delay helps
+ * to ensure that a meaningful number of items is present in the buffer
+ * before the buffer data is coalesced into a request.
+ */
+ private final static int COALESCING_DELAY_TIME = 100;
+
+ /**
+ * Maximum allowed number of requests in transmission to provider thread.
+ * This limit causes most of the client calls to be buffered at the input
+ * rather than in the request queue, which in truns allows the stale
+ * requests to be cancelled, before they are sent to the provider thread
+ * for processing.
+ */
+ private final static int REQUEST_QUEUE_SIZE_LIMIT = 100;
+
+ /** Delay before processing calls buffer, if the request queue is full */
+ private final static int REQUEST_BUFFER_FULL_RETRY_DELAY = PROCESSING_TIME;
+
+ /** Dispatch-thread executor that this provider uses. */
+ private DsfExecutor fExecutor;
+
+ /** List of listeners registered for events from provider. */
+ private List fListeners = new LinkedList();
+
+ /** Thread that handles data requests. */
+ private ProviderThread fProviderThread;
+
+ /** Queue of currently pending data requests. */
+ private final BlockingQueue fQueue = new DelayQueue();
+
+ /**
+ * Runnable to be submitted when the data provider thread is shut down.
+ * This variable acts like a flag: when client want to shut down the
+ * provider, it sets this runnable, and when the backgroun thread sees
+ * that it's set, it shuts itself down, and posts this runnable with
+ * the executor.
+ */
+ private RequestMonitor fShutdownRequestMonitor = null;
+
+ /**
+ * Buffers for coalescing getItem() calls into a single request.
+ */
+ private List fGetItemIndexesBuffer = new LinkedList();
+ private List> fGetItemRequestMonitorsBuffer = new LinkedList>();
+
+ /**
+ * Base class for requests that are queued by the data provider. It
+ * implements java.util.concurrent.Delayed to allow for use of DelayedQueue.
+ * Every request into the queue is delayed by the simulated transmission
+ * time.
+ */
+ private static abstract class Request implements Delayed {
+ /** Sequence counter and number are used to ensure FIFO order **/
+ private static int fSequenceCounter = 0;
+ private int fSequenceNumber = fSequenceCounter++;
+
+ /** Time delay tracks how items will be delayed. **/
+ private long fTime = System.currentTimeMillis() + TRANSMISSION_DELAY_TIME;
+
+ // @see java.util.concurrent.Delayed
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(fTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ // @see java.lang.Comparable
+ public int compareTo(Delayed other) {
+ if (other == this) // compare zero ONLY if same object
+ return 0;
+ Request x = (Request)other;
+ long diff = fTime - x.fTime;
+
+ if (diff < 0) return -1;
+ else if (diff > 0) return 1;
+ else if (fSequenceNumber < x.fSequenceNumber) return -1;
+ else return 1;
+ }
+
+ /** All requests have an associated array of RequestMonitor tokens **/
+ abstract RequestMonitor[] getRequestMonitors();
+ }
+
+ /**
+ * Object used to encapsulate the "getItemCount" requests. Instances of it
+ * are queued till processed.
+ */
+ private static class CountRequest extends Request
+ {
+ DataRequestMonitor fRequestMonitor;
+ CountRequest(DataRequestMonitor rm) { fRequestMonitor = rm; }
+ @Override
+ DataRequestMonitor>[] getRequestMonitors() { return new DataRequestMonitor[] { fRequestMonitor }; }
+ }
+
+ /**
+ * Object used to encapsulate the "getItem" requests. Instances of it
+ * are queued till processed.
+ */
+ private static class ItemRequest extends Request
+ {
+ DataRequestMonitor[] fRequestMonitors;
+ Integer[] fIndexes;
+ ItemRequest(Integer[] indexes, DataRequestMonitor[] rms) { fIndexes = indexes; fRequestMonitors = rms; }
+ @Override
+ DataRequestMonitor>[] getRequestMonitors() { return fRequestMonitors; }
+ }
+
+ /**
+ * The background thread of data provider. This thread retrieves the
+ * requests from the provider's queue and processes them. It also
+ * initiates random changes in the data set and issues corresponding
+ * events.
+ */
+ private class ProviderThread extends Thread
+ {
+ /**
+ * Current count of items in the data set. It is changed
+ * periodically for simulation purposes.
+ */
+ private int fCount = MIN_COUNT;
+
+ /**
+ * Incremented with every data change, it causes the count to be reset
+ * every four random changes.
+ */
+ private int fCountTrigger = 0;
+
+ /** Time when the last change was performed. */
+ private long fLastChangeTime = System.currentTimeMillis();
+
+ /** Random number generator */
+ private Random fRandom = new java.util.Random();
+
+ @Override
+ public void run() {
+ try {
+ // Initialize the count.
+ randomCount();
+
+ // Perform the loop until the shutdown runnable is set.
+ while(fShutdownRequestMonitor == null) {
+ // Get the next request from the queue. The time-out
+ // ensures that that we get to process the random changes.
+ final Request request = fQueue.poll(RANDOM_CHANGE_MILIS / 10, TimeUnit.MILLISECONDS);
+
+ // If a request was dequeued, process it.
+ if (request != null) {
+ // Simulate a processing delay.
+ Thread.sleep(PROCESSING_TIME);
+
+ if (request instanceof CountRequest) {
+ processCountRequest((CountRequest)request);
+ } else if (request instanceof ItemRequest) {
+ processItemRequest((ItemRequest)request);
+ }
+
+ // Whatever the results, post it to dispatch thread
+ // executor (with transmission delay).
+ fExecutor.schedule(
+ new DsfRunnable() {
+ public void run() {
+ for (RequestMonitor requestMonitor : request.getRequestMonitors()) {
+ requestMonitor.done();
+ }
+ }
+ },
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+
+ // Simulate data changes.
+ randomChanges();
+ }
+ }
+ catch (InterruptedException x) {
+ DsfExamplesPlugin.getDefault().getLog().log( new Status(
+ IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, 0, "Interrupted exception in slow data provider thread.", x )); //$NON-NLS-1$
+ }
+
+ // Notify the client that requested shutdown, that shutdown is complete.
+ fShutdownRequestMonitor.done();
+ fShutdownRequestMonitor = null;
+ }
+
+ private void processCountRequest(CountRequest request) {
+ // Calculate the simulated values.
+ request.fRequestMonitor.setData(fCount);
+ }
+
+ private void processItemRequest(ItemRequest request) {
+ // Check to make sure that the number of indexes matches the number
+ // of return tokens.
+ assert request.fRequestMonitors.length == request.fIndexes.length;
+
+ // Calculate the simulated values for each index in request.
+ for (int i = 0; i < request.fIndexes.length; i++) {
+ request.fRequestMonitors[i].setData(Long.toHexString(fLastChangeTime) + "." + request.fIndexes[i]); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This method simulates changes in provider's data set.
+ */
+ private void randomChanges()
+ {
+ if (System.currentTimeMillis() > fLastChangeTime + RANDOM_CHANGE_MILIS) {
+ fLastChangeTime = System.currentTimeMillis();
+ // once in every 30 seconds broadcast item count change
+ if (++fCountTrigger % RANDOM_COUNT_CHANGE_INTERVALS == 0) randomCount();
+ else randomDataChange();
+ }
+ }
+
+
+ /**
+ * Calculates new size for provider's data set.
+ */
+ private void randomCount()
+ {
+ fCount = MIN_COUNT + Math.abs(fRandom.nextInt()) % (MAX_COUNT - MIN_COUNT);
+
+ // Generate the event that the count has changed, and post it to
+ // dispatch thread with transmission delay.
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ for (Listener listener : fListeners) {
+ listener.countChanged();
+ }
+ }},
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+
+
+ /**
+ * Invalidates a random range of indexes.
+ */
+ private void randomDataChange()
+ {
+ final Set set = new HashSet();
+ // Change one in ten values.
+ for (int i = 0; i < fCount * RANDOM_CHANGE_SET_PERCENTAGE / 100; i++) {
+ set.add( new Integer(Math.abs(fRandom.nextInt()) % fCount) );
+ }
+
+ // Generate the event that the data has changed.
+ // Post dispatch thread with transmission delay.
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ for (Listener listener : fListeners) {
+ listener.dataChanged(set);
+ }
+ }},
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+ }
+
+
+ public CancellableInputCoalescingSlowDataProvider(DsfExecutor executor) {
+ fExecutor = executor;
+ fProviderThread = new ProviderThread();
+ fProviderThread.start();
+ }
+
+ /**
+ * Requests shutdown of this data provider.
+ * @param requestMonitor Monitor to call when shutdown is complete.
+ */
+ public void shutdown(RequestMonitor requestMonitor) {
+ fShutdownRequestMonitor = requestMonitor;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DataProvider
+ public DsfExecutor getDsfExecutor() {
+ return fExecutor;
+ }
+
+ public void getItemCount(final DataRequestMonitor rm) {
+ fQueue.add(new CountRequest(rm));
+ }
+
+ public void getItem(final int index, final DataRequestMonitor rm) {
+ // Schedule a buffer-servicing call, if one is needed.
+ if (fGetItemIndexesBuffer.isEmpty()) {
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ fileBufferedRequests();
+ }},
+ COALESCING_DELAY_TIME,
+ TimeUnit.MILLISECONDS);
+ }
+
+ // Add the call data to the buffer.
+ // Note: it doesn't matter that the items were added to the buffer
+ // after the buffer-servicing request was scheduled. This is because
+ // the buffers are guaranteed not to be modified until this dispatch
+ // cycle is over.
+ fGetItemIndexesBuffer.add(index);
+ fGetItemRequestMonitorsBuffer.add(rm);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void fileBufferedRequests() {
+ // Check if there is room in the request queue. If not, re-schedule the
+ // buffer-servicing for later.
+ if (fQueue.size() >= REQUEST_QUEUE_SIZE_LIMIT) {
+ if (fGetItemIndexesBuffer.isEmpty()) {
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ fileBufferedRequests();
+ }},
+ REQUEST_BUFFER_FULL_RETRY_DELAY,
+ TimeUnit.MILLISECONDS);
+ }
+ return;
+ }
+
+ // Remove a number of getItem() calls from the buffer, and combine them
+ // into a request.
+ List indexes = new LinkedList();
+ List rms = new LinkedList();
+ int numToCoalesce = 0;
+ while (!fGetItemIndexesBuffer.isEmpty() && numToCoalesce < COALESCING_COUNT_LIMIT) {
+ // Get the next call from buffer.
+ Integer index = fGetItemIndexesBuffer.remove(0);
+ DataRequestMonitor rm = fGetItemRequestMonitorsBuffer.remove(0);
+
+ // If call is already cancelled, ignore it.
+ if (rm.getStatus().getSeverity() == IStatus.CANCEL) continue;
+
+ // Otherwise add it to the request.
+ indexes.add(index);
+ rms.add(rm);
+ numToCoalesce++;
+ }
+
+ // Queue the coalesced request.
+ fQueue.add( new ItemRequest(
+ indexes.toArray(new Integer[indexes.size()]),
+ rms.toArray(new DataRequestMonitor[rms.size()]))
+ );
+
+ // If there are still calls left in the buffer, execute another
+ // buffer-servicing call, but without any delay.
+ if (!fGetItemIndexesBuffer.isEmpty()) {
+ fExecutor.execute(new Runnable() { public void run() {
+ fileBufferedRequests();
+ }});
+ }
+ }
+
+ public void addListener(Listener listener) {
+ assert fExecutor.isInExecutorThread();
+ fListeners.add(listener);
+ }
+
+ public void removeListener(Listener listener) {
+ assert fExecutor.isInExecutorThread();
+ fListeners.remove(listener);
+ }
+
+ //
+ ///////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellableInputCoalescingSlowDataProviderAction.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellableInputCoalescingSlowDataProviderAction.java
new file mode 100644
index 00000000000..32bee8ca4e4
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellableInputCoalescingSlowDataProviderAction.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import org.eclipse.dd.dsf.concurrent.DefaultDsfExecutor;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.actions.ActionDelegate;
+
+public class CancellableInputCoalescingSlowDataProviderAction extends ActionDelegate
+ implements IWorkbenchWindowActionDelegate
+{
+ private IWorkbenchWindow fWindow;
+
+ @Override
+ public void run(IAction action) {
+ if (fWindow != null) {
+ // Create the standard data provider.
+ final CancellableInputCoalescingSlowDataProvider dataProvider =
+ new CancellableInputCoalescingSlowDataProvider(new DefaultDsfExecutor());
+
+ // Create the dialog and open it.
+ Dialog dialog = new SlowDataProviderDialog(
+ fWindow.getShell(), new CancellingSlowDataProviderContentProvider(), dataProvider);
+ dialog.open();
+
+ // Shut down the data provider thread and the DSF executor thread.
+ // Note, since data provider is running in background thread, we have to
+ // wait until this background thread has completed shutdown before
+ // killing the executor thread itself.
+ dataProvider.shutdown(new RequestMonitor(dataProvider.getDsfExecutor(), null) {
+ @Override
+ public void handleCompleted() {
+ dataProvider.getDsfExecutor().shutdown();
+ }
+ });
+ }
+ }
+
+ public void init(IWorkbenchWindow window) {
+ fWindow = window;
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellingSlowDataProviderContentProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellingSlowDataProviderContentProvider.java
new file mode 100644
index 00000000000..dfb153fa6b1
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/CancellingSlowDataProviderContentProvider.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.dsf.concurrent.DsfExecutor;
+import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
+import org.eclipse.jface.viewers.ILazyContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Table;
+
+public class CancellingSlowDataProviderContentProvider
+ implements ILazyContentProvider, DataProvider.Listener
+{
+ TableViewer fTableViewer;
+ DataProvider fDataProvider;
+ Display fDisplay;
+ List fItemDataRequestMonitors = new LinkedList();
+ Set fCancelledIdxs = new HashSet();
+ AtomicInteger fCancelCallsPending = new AtomicInteger();
+
+ ///////////////////////////////////////////////////////////////////////////
+ // ILazyContentProvider
+ public void dispose() {
+ if (fDataProvider != null) {
+ final DataProvider dataProvider = fDataProvider;
+ dataProvider.getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ dataProvider.removeListener(CancellingSlowDataProviderContentProvider.this);
+ fTableViewer = null;
+ fDisplay = null;
+ fDataProvider = null;
+ }});
+ } else {
+ fTableViewer = null;
+ fDisplay = null;
+ }
+ }
+
+ public void inputChanged(final Viewer viewer, Object oldInput, final Object newInput) {
+ // If old data provider is not-null, unregister from it as listener.
+ if (fDataProvider != null) {
+ final DataProvider dataProvider = fDataProvider;
+ dataProvider.getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ dataProvider.removeListener(CancellingSlowDataProviderContentProvider.this);
+ }});
+ }
+
+
+ // Register as listener with new data provider.
+ // Note: if old data provider and new data provider use different executors,
+ // there is a chance of a race condition here.
+ if (newInput != null) {
+ ((DataProvider)newInput).getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ if ( ((TableViewer)viewer).getTable().isDisposed() ) return;
+ fTableViewer = (TableViewer)viewer;
+ fDisplay = fTableViewer.getTable().getDisplay();
+ fDataProvider = (DataProvider)newInput;
+ fDataProvider.addListener(CancellingSlowDataProviderContentProvider.this);
+ queryItemCount();
+ }});
+ }
+ }
+
+ public void updateElement(final int index) {
+ assert fTableViewer != null;
+ if (fDataProvider == null) return;
+
+ // Calculate the visible index range.
+ final int topIdx = fTableViewer.getTable().getTopIndex();
+ final int botIdx = topIdx + getVisibleItemCount(topIdx);
+
+ fCancelCallsPending.incrementAndGet();
+ fDataProvider.getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ // Must check again, in case disposed while re-dispatching.
+ if (fDataProvider == null || fTableViewer.getTable().isDisposed()) return;
+ if (index >= topIdx && index <= botIdx) {
+ queryItemData(index);
+ }
+ cancelStaleRequests(topIdx, botIdx);
+ }});
+ }
+
+ protected int getVisibleItemCount(int top) {
+ Table table = fTableViewer.getTable();
+ int itemCount = table.getItemCount();
+ return Math.min((table.getBounds().height / table.getItemHeight()) + 2, itemCount - top);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DataProvider.Listener
+ public void countChanged() {
+ // Check for dispose.
+ if (fDataProvider == null) return;
+
+ // Request new count.
+ queryItemCount();
+ }
+
+ public void dataChanged(final Set indexes) {
+ // Check for dispose.
+ if (fDataProvider == null) return;
+
+ // Clear changed items in table viewer.
+ final TableViewer tableViewer = fTableViewer;
+ fDisplay.asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (fTableViewer == null || fTableViewer.getTable().isDisposed()) return;
+ for (Integer index : indexes) {
+ tableViewer.clear(index);
+ }
+ }});
+ }
+ //
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Convenience extension to standard data return runnable. This extension
+ * automatically checks for errors and asynchronous dispose.
+ * @param
+ */
+ private abstract class CPGetDataRequestMonitor extends DataRequestMonitor {
+ public CPGetDataRequestMonitor(DsfExecutor executor) { super(executor, null); }
+ abstract protected void doRun();
+ @Override
+ public void handleCompleted() {
+ // If there is an error processing request, return.
+ if (!getStatus().isOK()) return;
+
+ // If content provider was disposed, return.
+ if (fTableViewer == null || fTableViewer.getTable().isDisposed()) return;
+
+ // Otherwise execute runnable.
+ doRun();
+ }
+ }
+
+ /**
+ * Executes the item count query with DataProvider. Must be called on
+ * data provider's dispatch thread.
+ */
+ private void queryItemCount() {
+ assert fDataProvider.getDsfExecutor().isInExecutorThread();
+
+ // Request coumt from data provider. When the count is returned, we
+ // have to re-dispatch into the display thread to avoid calling
+ // the table widget on the DSF dispatch thread.
+ fCancelledIdxs.clear();
+ fDataProvider.getItemCount(
+ new CPGetDataRequestMonitor(fDataProvider.getDsfExecutor()) {
+ @Override
+ protected void doRun() {
+ final TableViewer tableViewer = fTableViewer;
+ tableViewer.getTable().getDisplay().asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (tableViewer.getTable().isDisposed()) return; // disposed
+ tableViewer.setItemCount(getData());
+ tableViewer.getTable().clearAll();
+ }});
+ }});
+
+ }
+
+
+ /**
+ * Dedicated class for data item requests. This class holds the index
+ * argument so it can be examined when cancelling stale requests.
+ */
+ // Request data from data provider. Likewise, when the data is
+ // returned, we have to re-dispatch into the display thread to
+ // call the table widget.
+ class ItemGetDataRequestMonitor extends CPGetDataRequestMonitor {
+
+ /** Index is used when cancelling stale requests. */
+ int fIndex;
+
+ ItemGetDataRequestMonitor(DsfExecutor executor, int index) {
+ super(executor);
+ fIndex = index;
+ }
+
+ // Remove the request from list of outstanding requests. This has
+ // to be done in run() because doRun() is not always called.
+ @Override
+ public void handleCompleted() {
+ fItemDataRequestMonitors.remove(this);
+ super.handleCompleted();
+ }
+
+ // Process the result as usual.
+ @Override
+ protected void doRun() {
+ final TableViewer tableViewer = fTableViewer;
+ tableViewer.getTable().getDisplay().asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (tableViewer.getTable().isDisposed()) return; // disposed
+ tableViewer.replace(getData(), fIndex);
+ }});
+ }
+ }
+
+ /**
+ * Executes the data query with DataProvider. Must be called on dispatch
+ * thread.
+ * @param index Index of item to fetch.
+ */
+ private void queryItemData(final int index) {
+ assert fDataProvider.getDsfExecutor().isInExecutorThread();
+
+ ItemGetDataRequestMonitor rm = new ItemGetDataRequestMonitor(fDataProvider.getDsfExecutor(), index);
+ fItemDataRequestMonitors.add(rm);
+ fDataProvider.getItem(index, rm);
+ }
+
+ /**
+ * Iterates through the outstanding requests to data provider and
+ * cancells any that are nto visible any more.
+ * @param topIdx Top index of the visible items
+ * @param botIdx Bottom index of the visible items
+ */
+ private void cancelStaleRequests(int topIdx, int botIdx) {
+ // Go through the outstanding requests and cencel any that
+ // are not visible anymore.
+ for (Iterator itr = fItemDataRequestMonitors.iterator(); itr.hasNext();) {
+ ItemGetDataRequestMonitor item = itr.next();
+ if (item.fIndex < topIdx || item.fIndex > botIdx) {
+ // Set the item to cancelled status, so that the data provider
+ // will ignore it.
+ item.setStatus(new Status(IStatus.CANCEL, DsfExamplesPlugin.PLUGIN_ID, 0, "Cancelled", null)); //$NON-NLS-1$
+
+ // Add the item index to list of indexes that were cancelled,
+ // which will be sent to the table widget.
+ fCancelledIdxs.add(item.fIndex);
+
+ // Remove the item from the outstanding cancel requests.
+ itr.remove();
+ }
+ }
+ int cancelRequestsPending = fCancelCallsPending.decrementAndGet();
+ if (!fCancelledIdxs.isEmpty() && cancelRequestsPending == 0) {
+ final Set cancelledIdxs = fCancelledIdxs;
+ fCancelledIdxs = new HashSet();
+ final TableViewer tableViewer = fTableViewer;
+ tableViewer.getTable().getDisplay().asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (tableViewer.getTable().isDisposed()) return;
+
+ // Clear the indexes of the cancelled request, so that the
+ // viewer knows to request them again when needed.
+ // Note: clearing using TableViewer.clear(int) seems very
+ // inefficient, it's better to use Table.clear(int[]).
+ int[] cancelledIdxsArray = new int[cancelledIdxs.size()];
+ int i = 0;
+ for (Integer index : cancelledIdxs) {
+ cancelledIdxsArray[i++] = index;
+ }
+ tableViewer.getTable().clear(cancelledIdxsArray);
+ }});
+ }
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/DataProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/DataProvider.java
new file mode 100644
index 00000000000..b2990b1c2ca
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/DataProvider.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import java.util.Set;
+
+import org.eclipse.dd.dsf.concurrent.DsfExecutor;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+
+public interface DataProvider {
+
+ /**
+ * Interface for listeners for changes in Provider's data.
+ */
+ public interface Listener {
+ /**
+ * Indicates that the count of data items has changed.
+ */
+ void countChanged();
+
+ /**
+ * Indicates that some of the data values have changed.
+ * @param indexes Indexes of the changed items.
+ */
+ void dataChanged(Set indexes);
+ }
+
+ /**
+ * Returns the DSF executor that has to be used to call this data
+ * provider.
+ */
+ DsfExecutor getDsfExecutor();
+
+ /**
+ * Retrieves the current item count.
+ * @param rm Request monitor, to be filled in with the Integer value.
+ */
+ void getItemCount(DataRequestMonitor rm);
+
+ /**
+ * Retrieves data value for given index.
+ * @param index Index of the item to retrieve
+ * @param rm Return data token, to be filled in with a String value
+ */
+ void getItem(int index, DataRequestMonitor rm);
+
+ /**
+ * Registers given listener with data provider.
+ */
+ void addListener(Listener listener);
+
+ /**
+ * Removes given listener from data provider.
+ */
+ void removeListener(Listener listener);
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/InputCoalescingSlowDataProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/InputCoalescingSlowDataProvider.java
new file mode 100644
index 00000000000..62d2a910084
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/InputCoalescingSlowDataProvider.java
@@ -0,0 +1,408 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.dd.dsf.concurrent.DsfRunnable;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.dd.dsf.concurrent.DsfExecutor;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
+
+/**
+ * Example data provider which has a built-in delay when fetching data. This
+ * data provider simulates a service which retrieves data from an external
+ * source such as a networked target, which incurs a considerable delay when
+ * retrieving data. The data items are simulated values which consist of the
+ * time when data is being retrieved followed by the item's index.
+ *
+ * This version of the data provider features an optimization which causes
+ * item requests to be grouped together even before they are filed into the
+ * processing queue. This example demonstrates how the service can implement
+ * coalescing impelemntation in a situation where the provider has an
+ * interface which only accepts aggregate requests, so the requests have to be
+ * coalesed before they are sent to the provider.
+ */
+public class InputCoalescingSlowDataProvider implements DataProvider {
+
+ /** Minimum count of data items */
+ private final static int MIN_COUNT = 1000;
+
+ /** Maximum count of data items */
+ private final static int MAX_COUNT = 2000;
+
+ /** Time interval how often random changes occur. */
+ private final static int RANDOM_CHANGE_MILIS = 10000;
+
+ /** Number of times random changes are made, before count is changed. */
+ private final static int RANDOM_COUNT_CHANGE_INTERVALS = 3;
+
+ /** Percentage of values that is changed upon random change (0-100). */
+ private final static int RANDOM_CHANGE_SET_PERCENTAGE = 10;
+
+ /**
+ * Amount of time (in miliseconds) how long the requests to provider, and
+ * events from provider are delayed by.
+ */
+ private final static int TRANSMISSION_DELAY_TIME = 500;
+
+ /**
+ * Amount of time (in milliseconds) how long the provider takes to process
+ * a request.
+ */
+ private final static int PROCESSING_TIME = 100;
+
+ /**
+ * Maximum number of item requests that can be coalesced into a single
+ * request.
+ */
+ private final static int COALESCING_COUNT_LIMIT = 10;
+
+ /**
+ * Delay in processing the buffer of getItem() calls. This delay helps
+ * to ensure that a meaningful number of items is present in the buffer
+ * before the buffer data is coalesced into a request.
+ */
+ private final static int COALESCING_DELAY_TIME = 10;
+
+ /** Dispatch-thread executor that this provider uses. */
+ private DsfExecutor fExecutor;
+
+ /** List of listeners registered for events from provider. */
+ private List fListeners = new LinkedList();
+
+ /** Thread that handles data requests. */
+ private ProviderThread fProviderThread;
+
+ /** Queue of currently pending data requests. */
+ private final BlockingQueue fQueue = new DelayQueue();
+
+ /**
+ * Runnable to be submitted when the data provider thread is shut down.
+ * This variable acts like a flag: when client want to shut down the
+ * provider, it sets this runnable, and when the backgroun thread sees
+ * that it's set, it shuts itself down, and posts this runnable with
+ * the executor.
+ */
+ private RequestMonitor fShutdownRequestMonitor = null;
+
+ /**
+ * Buffers for coalescing getItem() calls into a single request.
+ */
+ private List fGetItemIndexesBuffer = new LinkedList();
+ private List> fGetItemRequestMonitorsBuffer = new LinkedList>();
+
+ /**
+ * Base class for requests that are queued by the data provider. It
+ * implements java.util.concurrent.Delayed to allow for use of DelayedQueue.
+ * Every request into the queue is delayed by the simulated transmission
+ * time.
+ */
+ private static abstract class Request implements Delayed {
+ /** Sequence counter and number are used to ensure FIFO order **/
+ private static int fSequenceCounter = 0;
+ private int fSequenceNumber = fSequenceCounter++;
+
+ /** Time delay tracks how items will be delayed. **/
+ private long fTime = System.currentTimeMillis() + TRANSMISSION_DELAY_TIME;
+
+ // @see java.util.concurrent.Delayed
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(fTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ // @see java.lang.Comparable
+ public int compareTo(Delayed other) {
+ if (other == this) // compare zero ONLY if same object
+ return 0;
+ Request x = (Request)other;
+ long diff = fTime - x.fTime;
+
+ if (diff < 0) return -1;
+ else if (diff > 0) return 1;
+ else if (fSequenceNumber < x.fSequenceNumber) return -1;
+ else return 1;
+ }
+
+ /** All requests have an associated array of RequestMonitor tokens **/
+ abstract RequestMonitor[] getRequestMonitors();
+ }
+
+ /**
+ * Object used to encapsulate the "getItemCount" requests. Instances of it
+ * are queued till processed.
+ */
+ private static class CountRequest extends Request
+ {
+ DataRequestMonitor fRequestMonitors;
+ CountRequest(DataRequestMonitor rms) { fRequestMonitors = rms; }
+ @Override
+ DataRequestMonitor>[] getRequestMonitors() { return new DataRequestMonitor[] { fRequestMonitors }; }
+ }
+
+ /**
+ * Object used to encapsulate the "getItem" requests. Instances of it
+ * are queued till processed.
+ */
+ private static class ItemRequest extends Request
+ {
+ DataRequestMonitor[] fRequestMonitors;
+ Integer[] fIndexes;
+ ItemRequest(Integer[] indexes, DataRequestMonitor[] rms) { fIndexes = indexes; fRequestMonitors = rms; }
+ @Override
+ DataRequestMonitor>[] getRequestMonitors() { return fRequestMonitors; }
+ }
+
+ /**
+ * The background thread of data provider. This thread retrieves the
+ * requests from the provider's queue and processes them. It also
+ * initiates random changes in the data set and issues corresponding
+ * events.
+ */
+ private class ProviderThread extends Thread
+ {
+ /**
+ * Current count of items in the data set. It is changed
+ * periodically for simulation purposes.
+ */
+ private int fCount = MIN_COUNT;
+
+ /**
+ * Incremented with every data change, it causes the count to be reset
+ * every four random changes.
+ */
+ private int fCountTrigger = 0;
+
+ /** Time when the last change was performed. */
+ private long fLastChangeTime = System.currentTimeMillis();
+
+ /** Random number generator */
+ private Random fRandom = new java.util.Random();
+
+ @Override
+ public void run() {
+ try {
+ // Initialize the count.
+ randomCount();
+
+ // Perform the loop until the shutdown runnable is set.
+ while(fShutdownRequestMonitor == null) {
+ // Get the next request from the queue. The time-out
+ // ensures that that we get to process the random changes.
+ final Request request = fQueue.poll(RANDOM_CHANGE_MILIS / 10, TimeUnit.MILLISECONDS);
+
+ // If a request was dequeued, process it.
+ if (request != null) {
+ // Simulate a processing delay.
+ Thread.sleep(PROCESSING_TIME);
+
+ if (request instanceof CountRequest) {
+ processCountRequest((CountRequest)request);
+ } else if (request instanceof ItemRequest) {
+ processItemRequest((ItemRequest)request);
+ }
+ // Whatever the results, post it to dispatch thread
+ // executor (with transmission delay).
+ fExecutor.schedule(
+ new DsfRunnable() {
+ public void run() {
+ for (RequestMonitor requestMonitor : request.getRequestMonitors()) {
+ requestMonitor.done();
+ }
+ }
+ },
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+
+ // Simulate data changes.
+ randomChanges();
+ }
+ }
+ catch (InterruptedException x) {
+ DsfExamplesPlugin.getDefault().getLog().log( new Status(
+ IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, 0, "Interrupted exception in slow data provider thread.", x )); //$NON-NLS-1$
+ }
+
+ // Notify the client that requested shutdown, that shutdown is complete.
+ fShutdownRequestMonitor.done();
+ fShutdownRequestMonitor = null;
+ }
+
+ private void processCountRequest(CountRequest request) {
+ // Calculate the simulated values.
+ request.fRequestMonitors.setData(fCount);
+ }
+
+ private void processItemRequest(ItemRequest request) {
+ // Check to make sure that the number of indexes matches the number
+ // of return tokens.
+ assert request.fRequestMonitors.length == request.fIndexes.length;
+
+ // Calculate the simulated values for each index in request.
+ for (int i = 0; i < request.fIndexes.length; i++) {
+ request.fRequestMonitors[i].setData(Long.toHexString(fLastChangeTime) + "." + request.fIndexes[i]); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This method simulates changes in provider's data set.
+ */
+ private void randomChanges()
+ {
+ if (System.currentTimeMillis() > fLastChangeTime + RANDOM_CHANGE_MILIS) {
+ fLastChangeTime = System.currentTimeMillis();
+ // once in every 30 seconds broadcast item count change
+ if (++fCountTrigger % RANDOM_COUNT_CHANGE_INTERVALS == 0) randomCount();
+ else randomDataChange();
+ }
+ }
+
+
+ /**
+ * Calculates new size for provider's data set.
+ */
+ private void randomCount()
+ {
+ fCount = MIN_COUNT + Math.abs(fRandom.nextInt()) % (MAX_COUNT - MIN_COUNT);
+
+ // Generate the event that the count has changed, and post it to
+ // dispatch thread with transmission delay.
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ for (Listener listener : fListeners) {
+ listener.countChanged();
+ }
+ }},
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+
+
+ /**
+ * Invalidates a random range of indexes.
+ */
+ private void randomDataChange()
+ {
+ final Set set = new HashSet();
+ // Change one in ten values.
+ for (int i = 0; i < fCount * RANDOM_CHANGE_SET_PERCENTAGE / 100; i++) {
+ set.add( new Integer(Math.abs(fRandom.nextInt()) % fCount) );
+ }
+
+ // Generate the event that the data has changed.
+ // Post dispatch thread with transmission delay.
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ for (Listener listener : fListeners) {
+ listener.dataChanged(set);
+ }
+ }},
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+ }
+
+
+ public InputCoalescingSlowDataProvider(DsfExecutor executor) {
+ fExecutor = executor;
+ fProviderThread = new ProviderThread();
+ fProviderThread.start();
+ }
+
+ /**
+ * Requests shutdown of this data provider.
+ * @param requestMonitor Monitor to call when shutdown is complete.
+ */
+ public void shutdown(RequestMonitor requestMonitor) {
+ fShutdownRequestMonitor = requestMonitor;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DataProvider
+ public DsfExecutor getDsfExecutor() {
+ return fExecutor;
+ }
+
+ public void getItemCount(final DataRequestMonitor rm) {
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ fQueue.add(new CountRequest(rm));
+ }},
+ TRANSMISSION_DELAY_TIME,
+ TimeUnit.MILLISECONDS);
+ }
+
+ public void getItem(final int index, final DataRequestMonitor rm) {
+ // Schedule a buffer-servicing call, if one is needed.
+ if (fGetItemIndexesBuffer.isEmpty()) {
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ fileBufferedRequests();
+ }},
+ COALESCING_DELAY_TIME,
+ TimeUnit.MILLISECONDS);
+ }
+
+ // Add the call data to the buffer.
+ // Note: it doesn't matter that the items were added to the buffer
+ // after the buffer-servicing request was scheduled. This is because
+ // the buffers are guaranteed not to be modified until this dispatch
+ // cycle is over.
+ fGetItemIndexesBuffer.add(index);
+ fGetItemRequestMonitorsBuffer.add(rm);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void fileBufferedRequests() {
+ // Remove a number of getItem() calls from the buffer, and combine them
+ // into a request.
+ int numToCoalesce = Math.min(fGetItemIndexesBuffer.size(), COALESCING_COUNT_LIMIT);
+ final ItemRequest request = new ItemRequest(new Integer[numToCoalesce], new DataRequestMonitor[numToCoalesce]);
+ for (int i = 0; i < numToCoalesce; i++) {
+ request.fIndexes[i] = fGetItemIndexesBuffer.remove(0);
+ request.fRequestMonitors[i] = fGetItemRequestMonitorsBuffer.remove(0);
+ }
+
+ // Queue the coalesced request, with the appropriate transmission delay.
+ fQueue.add(request);
+
+ // If there are still calls left in the buffer, execute another
+ // buffer-servicing call, but without any delay.
+ if (!fGetItemIndexesBuffer.isEmpty()) {
+ fExecutor.execute(new Runnable() { public void run() {
+ fileBufferedRequests();
+ }});
+ }
+ }
+
+ public void addListener(Listener listener) {
+ assert fExecutor.isInExecutorThread();
+ fListeners.add(listener);
+ }
+
+ public void removeListener(Listener listener) {
+ assert fExecutor.isInExecutorThread();
+ fListeners.remove(listener);
+ }
+
+ //
+ ///////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/InputCoalescingSlowDataProviderAction.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/InputCoalescingSlowDataProviderAction.java
new file mode 100644
index 00000000000..dccf8b8d92f
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/InputCoalescingSlowDataProviderAction.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import org.eclipse.dd.dsf.concurrent.DefaultDsfExecutor;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.actions.ActionDelegate;
+
+public class InputCoalescingSlowDataProviderAction extends ActionDelegate
+ implements IWorkbenchWindowActionDelegate
+{
+ private IWorkbenchWindow fWindow;
+
+ @Override
+ public void run(IAction action) {
+ if (fWindow != null) {
+ // Create the standard data provider.
+ final InputCoalescingSlowDataProvider dataProvider =
+ new InputCoalescingSlowDataProvider(new DefaultDsfExecutor());
+
+ // Create the dialog and open it.
+ Dialog dialog = new SlowDataProviderDialog(
+ fWindow.getShell(), new SlowDataProviderContentProvider(), dataProvider);
+ dialog.open();
+
+ // Shut down the data provider thread and the DSF executor thread.
+ // Note, since data provider is running in background thread, we have to
+ // wait until this background thread has completed shutdown before
+ // killing the executor thread itself.
+ dataProvider.shutdown(new RequestMonitor(dataProvider.getDsfExecutor(), null) {
+ @Override
+ public void handleCompleted() {
+ dataProvider.getDsfExecutor().shutdown();
+ }
+ });
+ }
+ }
+
+ public void init(IWorkbenchWindow window) {
+ fWindow = window;
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProvider.java
new file mode 100644
index 00000000000..f0c350581d9
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProvider.java
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.dd.dsf.concurrent.DsfRunnable;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.dd.dsf.concurrent.DsfExecutor;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
+
+/**
+ * Example data provider which has a built-in delay when fetching data. This
+ * data provider simulates a service which retrieves data from an external
+ * source such as a networked target, which incurs a considerable delay when
+ * retrieving data. The data items are simulated values which consist of the
+ * time when data is being retrieved followed by the item's index.
+ */
+public class SlowDataProvider implements DataProvider {
+
+ /** Minimum count of data items */
+ private final static int MIN_COUNT = 1000;
+
+ /** Maximum count of data items */
+ private final static int MAX_COUNT = 2000;
+
+ /** Time interval how often random changes occur. */
+ private final static int RANDOM_CHANGE_MILIS = 10000;
+
+ /** Number of times random changes are made, before count is changed. */
+ private final static int RANDOM_COUNT_CHANGE_INTERVALS = 3;
+
+ /** Percentage of values that is changed upon random change (0-100). */
+ private final static int RANDOM_CHANGE_SET_PERCENTAGE = 10;
+
+ /**
+ * Amount of time (in miliseconds) how long the requests to provider, and
+ * events from provider are delayed by.
+ */
+ private final static int TRANSMISSION_DELAY_TIME = 500;
+
+ /**
+ * Amount of time (in milliseconds) how long the provider takes to process
+ * a request.
+ */
+ private final static int PROCESSING_TIME = 100;
+
+ /** Dispatch-thread executor that this provider uses. */
+ private DsfExecutor fExecutor;
+
+ /** List of listeners registered for events from provider. */
+ private List fListeners = new LinkedList();
+
+ /** Thread that handles data requests. */
+ private ProviderThread fProviderThread;
+
+ /** Queue of currently pending data requests. */
+ private final BlockingQueue fQueue = new DelayQueue();
+
+ /**
+ * Runnable to be submitted when the data provider thread is shut down.
+ * This variable acts like a flag: when client want to shut down the
+ * provider, it sets this runnable, and when the backgroun thread sees
+ * that it's set, it shuts itself down, and posts this runnable with
+ * the executor.
+ */
+ private RequestMonitor fShutdownRequestMonitor = null;
+
+ /**
+ * Base class for requests that are queued by the data provider. It
+ * implements java.util.concurrent.Delayed to allow for use of DelayedQueue.
+ * Every request into the queue is delayed by the simulated transmission
+ * time.
+ */
+ private static abstract class Request implements Delayed {
+ /** Sequence counter and number are used to ensure FIFO order **/
+ private static int fSequenceCounter = 0;
+ private int fSequenceNumber = fSequenceCounter++;
+
+ /** Time delay tracks how items will be delayed. **/
+ private long fTime = System.currentTimeMillis() + TRANSMISSION_DELAY_TIME;
+
+ // @see java.util.concurrent.Delayed
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(fTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ // @see java.lang.Comparable
+ public int compareTo(Delayed other) {
+ if (other == this) // compare zero ONLY if same object
+ return 0;
+ Request x = (Request)other;
+ long diff = fTime - x.fTime;
+
+ if (diff < 0) return -1;
+ else if (diff > 0) return 1;
+ else if (fSequenceNumber < x.fSequenceNumber) return -1;
+ else return 1;
+ }
+
+ /** All requests have an associated RequestMonitor token **/
+ abstract RequestMonitor getRequestMonitor();
+ }
+
+ /**
+ * Object used to encapsulate the "getItemCount" requests. Instances of it
+ * are queued till processed.
+ */
+ private static class CountRequest extends Request
+ {
+ DataRequestMonitor fRequestMonitor;
+ CountRequest(DataRequestMonitor rm) { fRequestMonitor = rm; }
+ @Override
+ DataRequestMonitor> getRequestMonitor() { return fRequestMonitor; }
+ }
+
+ /**
+ * Object used to encapsulate the "getItem" requests. Instances of it
+ * are queued till processed.
+ */
+ private static class ItemRequest extends Request
+ {
+ DataRequestMonitor fRequestMonitor;
+ int fIndex;
+ ItemRequest(int index, DataRequestMonitor rm) { fIndex = index; fRequestMonitor = rm; }
+ @Override
+ DataRequestMonitor> getRequestMonitor() { return fRequestMonitor; }
+ }
+
+ /**
+ * The background thread of data provider. This thread retrieves the
+ * requests from the provider's queue and processes them. It also
+ * initiates random changes in the data set and issues corresponding
+ * events.
+ */
+ private class ProviderThread extends Thread
+ {
+ /**
+ * Current count of items in the data set. It is changed
+ * periodically for simulation purposes.
+ */
+ private int fCount = MIN_COUNT;
+
+ /**
+ * Incremented with every data change, it causes the count to be reset
+ * every four random changes.
+ */
+ private int fCountTrigger = 0;
+
+ /** Time when the last change was performed. */
+ private long fLastChangeTime = System.currentTimeMillis();
+
+ /** Random number generator */
+ private Random fRandom = new java.util.Random();
+
+ @Override
+ public void run() {
+ try {
+ // Initialize the count.
+ randomCount();
+
+ // Perform the loop until the shutdown runnable is set.
+ while(fShutdownRequestMonitor == null) {
+ // Get the next request from the queue. The time-out
+ // ensures that that we get to process the random changes.
+ final Request request = fQueue.poll(RANDOM_CHANGE_MILIS / 10, TimeUnit.MILLISECONDS);
+
+ // If a request was dequeued, process it.
+ if (request != null) {
+ // Simulate a processing delay.
+ Thread.sleep(PROCESSING_TIME);
+
+ if (request instanceof CountRequest) {
+ processCountRequest((CountRequest)request);
+ } else if (request instanceof ItemRequest) {
+ processItemRequest((ItemRequest)request);
+ }
+ // Whatever the result, post it to dispatch thread
+ // executor (with transmission delay).
+ fExecutor.schedule(
+ new DsfRunnable() {
+ public void run() {
+ request.getRequestMonitor().done();
+ }
+ },
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+
+ // Simulate data changes.
+ randomChanges();
+ }
+ }
+ catch (InterruptedException x) {
+ DsfExamplesPlugin.getDefault().getLog().log( new Status(
+ IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, 0, "Interrupted exception in slow data provider thread.", x )); //$NON-NLS-1$
+ }
+
+ // Notify the client that requested shutdown, that shutdown is complete.
+ fShutdownRequestMonitor.done();
+ fShutdownRequestMonitor = null;
+ }
+
+ private void processCountRequest(CountRequest request) {
+ // Calculate the simulated values.
+ request.fRequestMonitor.setData(fCount);
+ }
+
+ private void processItemRequest(ItemRequest request) {
+ // Calculate the simulated values.
+ request.fRequestMonitor.setData(Long.toHexString(fLastChangeTime) + "." + request.fIndex); //$NON-NLS-1$
+ }
+
+ /**
+ * This method simulates changes in provider's data set.
+ */
+ private void randomChanges()
+ {
+ if (System.currentTimeMillis() > fLastChangeTime + RANDOM_CHANGE_MILIS) {
+ fLastChangeTime = System.currentTimeMillis();
+ // once in every 30 seconds broadcast item count change
+ if (++fCountTrigger % RANDOM_COUNT_CHANGE_INTERVALS == 0) randomCount();
+ else randomDataChange();
+ }
+ }
+
+
+ /**
+ * Calculates new size for provider's data set.
+ */
+ private void randomCount()
+ {
+ fCount = MIN_COUNT + Math.abs(fRandom.nextInt()) % (MAX_COUNT - MIN_COUNT);
+
+ // Generate the event that the count has changed, and post it to
+ // dispatch thread with transmission delay.
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ for (Listener listener : fListeners) {
+ listener.countChanged();
+ }
+ }},
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+
+
+ /**
+ * Invalidates a random range of indexes.
+ */
+ private void randomDataChange()
+ {
+ final Set set = new HashSet();
+ // Change one in ten values.
+ for (int i = 0; i < fCount * RANDOM_CHANGE_SET_PERCENTAGE / 100; i++) {
+ set.add( new Integer(Math.abs(fRandom.nextInt()) % fCount) );
+ }
+
+ // Generate the event that the data has changed.
+ // Post dispatch thread with transmission delay.
+ fExecutor.schedule(
+ new Runnable() { public void run() {
+ for (Listener listener : fListeners) {
+ listener.dataChanged(set);
+ }
+ }},
+ TRANSMISSION_DELAY_TIME, TimeUnit.MILLISECONDS);
+ }
+ }
+
+
+ public SlowDataProvider(DsfExecutor executor) {
+ fExecutor = executor;
+ fProviderThread = new ProviderThread();
+ fProviderThread.start();
+ }
+
+ /**
+ * Requests shutdown of this data provider.
+ * @param requestMonitor Request completion monitor.
+ */
+ public void shutdown(RequestMonitor requestMonitor) {
+ fShutdownRequestMonitor = requestMonitor;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DataProvider
+ public DsfExecutor getDsfExecutor() {
+ return fExecutor;
+ }
+
+ public void getItemCount(final DataRequestMonitor rm) {
+ fQueue.add(new CountRequest(rm));
+ }
+
+ public void getItem(final int index, final DataRequestMonitor rm) {
+ fQueue.add(new ItemRequest(index, rm));
+ }
+
+ public void addListener(Listener listener) {
+ assert fExecutor.isInExecutorThread();
+ fListeners.add(listener);
+ }
+
+ public void removeListener(Listener listener) {
+ assert fExecutor.isInExecutorThread();
+ fListeners.remove(listener);
+ }
+
+ //
+ ///////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderAction.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderAction.java
new file mode 100644
index 00000000000..77c0c37e192
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderAction.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import org.eclipse.dd.dsf.concurrent.DefaultDsfExecutor;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.actions.ActionDelegate;
+
+public class SlowDataProviderAction extends ActionDelegate
+ implements IWorkbenchWindowActionDelegate
+{
+ private IWorkbenchWindow fWindow;
+
+ @Override
+ public void run(IAction action) {
+ if (fWindow != null) {
+ // Create the standard data provider.
+ final SlowDataProvider dataProvider = new SlowDataProvider(new DefaultDsfExecutor());
+
+ // Create the dialog and open it.
+ Dialog dialog = new SlowDataProviderDialog(
+ fWindow.getShell(), new SlowDataProviderContentProvider(), dataProvider);
+ dialog.open();
+
+ // Shut down the data provider thread and the DSF executor thread.
+ // Note, since data provider is running in background thread, we have to
+ // wait until this background thread has completed shutdown before
+ // killing the executor thread itself.
+ dataProvider.shutdown(new RequestMonitor(dataProvider.getDsfExecutor(), null) {
+ @Override
+ public void handleCompleted() {
+ dataProvider.getDsfExecutor().shutdown();
+ }
+ });
+ }
+ }
+
+ public void init(IWorkbenchWindow window) {
+ fWindow = window;
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderContentProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderContentProvider.java
new file mode 100644
index 00000000000..a71d5019c8c
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderContentProvider.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import java.util.Set;
+
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.dsf.concurrent.DsfExecutor;
+import org.eclipse.jface.viewers.ILazyContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+
+public class SlowDataProviderContentProvider
+ implements ILazyContentProvider, DataProvider.Listener
+{
+
+ TableViewer fTableViewer;
+ DataProvider fDataProvider;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // ILazyContentProvider
+ public void dispose() {
+ if (fDataProvider != null) {
+ final DataProvider dataProvider = fDataProvider;
+ dataProvider.getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ dataProvider.removeListener(SlowDataProviderContentProvider.this);
+ fTableViewer = null;
+ fDataProvider = null;
+ }});
+ } else {
+ fTableViewer = null;
+ }
+ }
+
+ public void inputChanged(final Viewer viewer, Object oldInput, final Object newInput) {
+ // If old data provider is not-null, unregister from it as listener.
+ if (fDataProvider != null) {
+ final DataProvider dataProvider = fDataProvider;
+ dataProvider.getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ dataProvider.removeListener(SlowDataProviderContentProvider.this);
+ }});
+ }
+
+
+ // Register as listener with new data provider.
+ // Note: if old data provider and new data provider use different executors,
+ // there is a chance of a race condition here.
+ if (newInput != null) {
+ ((DataProvider)newInput).getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ fTableViewer = (TableViewer)viewer;
+ fDataProvider = (DataProvider)newInput;
+ fDataProvider.addListener(SlowDataProviderContentProvider.this);
+ queryItemCount();
+ }});
+ }
+ }
+
+ public void updateElement(final int index) {
+ assert fTableViewer != null;
+ if (fDataProvider == null) return;
+
+ fDataProvider.getDsfExecutor().execute(
+ new Runnable() { public void run() {
+ // Must check again, in case disposed while re-dispatching.
+ if (fDataProvider == null) return;
+
+ queryItemData(index);
+ }});
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DataProvider.Listener
+ public void countChanged() {
+ // Check for dispose.
+ if (fDataProvider == null) return;
+
+ // Request new count.
+ queryItemCount();
+ }
+
+ public void dataChanged(final Set indexes) {
+ // Check for dispose.
+ if (fDataProvider == null) return;
+
+ // Clear changed items in table viewer.
+ if (fTableViewer != null) {
+ final TableViewer tableViewer = fTableViewer;
+ tableViewer.getTable().getDisplay().asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (tableViewer.getTable().isDisposed()) return; // disposed
+ for (Integer index : indexes) {
+ tableViewer.clear(index);
+ }
+ }});
+ }
+ }
+ //
+ ///////////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Convenience extension to standard data return runnable. This extension
+ * automatically checks for errors and asynchronous dipose.
+ * @param
+ */
+ private abstract class CPGetDataRequestMonitor extends DataRequestMonitor {
+ CPGetDataRequestMonitor(DsfExecutor executor) { super(executor, null); }
+ abstract protected void doRun();
+ @Override
+ final public void handleCompleted() {
+ // If there is an error processing request, return.
+ if (!getStatus().isOK()) return;
+
+ // If content provider was disposed, return.
+ if (fTableViewer == null) return;
+
+ // Otherwise execute runnable.
+ doRun();
+ }
+ }
+
+ /**
+ * Executes the item count query with DataProvider. Must be called on
+ * data provider's dispatch thread.
+ */
+ private void queryItemCount() {
+ assert fDataProvider.getDsfExecutor().isInExecutorThread();
+
+ // Request coumt from data provider. When the count is returned, we
+ // have to re-dispatch into the display thread to avoid calling
+ // the table widget on the DSF dispatch thread.
+ fDataProvider.getItemCount(
+ new CPGetDataRequestMonitor(fDataProvider.getDsfExecutor()) {
+ @Override
+ protected void doRun() {
+ final TableViewer tableViewer = fTableViewer;
+ tableViewer.getTable().getDisplay().asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (tableViewer.getTable().isDisposed()) return; // disposed
+ tableViewer.setItemCount(getData());
+ tableViewer.getTable().clearAll();
+ }});
+ }});
+
+ }
+
+ /**
+ * Executes the data query with DataProvider. Must be called on dispatch
+ * thread.
+ * @param index Index of item to fetch.
+ */
+ private void queryItemData(final int index) {
+ assert fDataProvider.getDsfExecutor().isInExecutorThread();
+
+ // Request data from data provider. Likewise, when the data is
+ // returned, we have to re-dispatch into the display thread to
+ // call the table widget.
+ fDataProvider.getItem(
+ index,
+ new CPGetDataRequestMonitor(fDataProvider.getDsfExecutor()) {
+ @Override
+ protected void doRun() {
+ final TableViewer tableViewer = fTableViewer;
+ tableViewer.getTable().getDisplay().asyncExec(
+ new Runnable() { public void run() {
+ // Check again if table wasn't disposed when
+ // switching to the display thread.
+ if (tableViewer.getTable().isDisposed()) return; // disposed
+ tableViewer.replace(getData(), index);
+ }});
+ }});
+ }
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderDialog.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderDialog.java
new file mode 100644
index 00000000000..4c763724658
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/SlowDataProviderDialog.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.concurrent;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Dialog shared by all slow data provider examples. It accepts the data
+ * provider and the content provider as arguments to the constructor. So the
+ * only thing that the dialog does is to create the table viewer and
+ * initialize it with the providers.
+ */
+public class SlowDataProviderDialog extends Dialog {
+
+ private TableViewer fDataViewer;
+ private DataProvider fDataProvider;
+ private IContentProvider fContentProvider;
+
+ public SlowDataProviderDialog(Shell parent, IContentProvider contentProvider, DataProvider dataProvider) {
+ super(parent);
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ fContentProvider = contentProvider;
+ fDataProvider = dataProvider;
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite area = (Composite) super.createDialogArea(parent);
+ fDataViewer = new TableViewer(area, SWT.VIRTUAL);
+ fDataViewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));
+ fDataViewer.setContentProvider(fContentProvider);
+ fDataViewer.setInput(fDataProvider);
+ return area;
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/doc-files/dsf_concurrency_model-1.png b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/doc-files/dsf_concurrency_model-1.png
new file mode 100644
index 00000000000..1bb373447d7
Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/doc-files/dsf_concurrency_model-1.png differ
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/package.html b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/package.html
new file mode 100644
index 00000000000..f0efe705346
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/concurrent/package.html
@@ -0,0 +1,289 @@
+
+
+
+
+ DSF Slow Data Provider Example
+
+
+
+The point of DSF concurrency can be most easily explained through
+a practical example. Suppose there is a viewer which needs to
+show data that originates from a remote "provider". There is a
+considerable delay in transmitting the data to and from the provider,
+and some delay in processing the data. The viewer is a
+lazy-loading table, which means that it request information only about
+items that are visible on the screen, and as the table is scrolled, new
+requests for data are generated. The diagram below illustrates
+the
+logical relationship between components:
+
+.
+
In detail, these components look like this:
+
+Table Viewer
+
The table viewer is the standard
+org.eclipse.jface.viewers.TableViewer,
+created with SWT.VIRTUAL
+flag. It has an associated content
+provider, SlowDataProviderContentProvider) which handles all the
+interactions with the data provider. The lazy content provider
+operates in a very simple cycle:
+
+
Table viewer tells content provider that the input has changed by
+calling IContentProvider.inputChanged().
+This means that the content provider has to query initial state of the
+data.
+
Next the content provider tells the viewer how many elements
+there are, by calling TableViewer.setItemCount().
+
At this point, the table resizes, and it requests data values for
+items that are visible. So for each visible item it calls: ILazyContentProvider.updateElement().
+
After calculating the value, the content provider tells the table
+what the value is, by calling TableViewer.replace().
+
If the data ever changes, the content provider tells the table to
+rerequest the data, by calling TableViewer.clear().
+
+Table viewer operates in the
+SWT display thread, which means that the content provider must switch
+from the display thread to the DSF dispatch thread, whenever it is
+called by the table viewer, as in the example below:
+
public void updateElement(final int index) { assert fTableViewer != null; if (fDataProvider == null) return;
fDataProvider.getExecutor().execute( new Runnable() { public void run() { // Must check again, in case disposed while redispatching. if (fDataProvider == null) return;
queryItemData(index); }}); }
+Likewise, when the content provider calls the table viewer, it also has
+to switch back into the display thread as in following example, when
+the content provider receives an event from the data provider, that an
+item value has changed.
+
public void dataChanged(final Set<Integer> indexes) { // Check for dispose. if (fDataProvider == null) return;
// Clear changed items in table viewer. if (fTableViewer != null) { final TableViewer tableViewer = fTableViewer; tableViewer.getTable().getDisplay().asyncExec( new Runnable() { public void run() { // Check again if table wasn't disposed when // switching to the display thread. if (tableViewer.getTable().isDisposed()) return; // disposed for (Integer index : indexes) { tableViewer.clear(index); } }}); } }
+All of this switching back and forth between threads makes the code
+look a lot more complicated than it really is, and it takes some
+getting used to, but this is the price to be paid for multi-threading.
+Whether the participants use semaphores or the dispatch thread, the
+logic is equally complicated, and we believe that using a single
+dispatch thread, makes the synchronization very explicit and thus less
+error-prone.
+
Data Provider Service
+
The data provider service interface, DataProvider, is very similar
+to that of the lazy content provider. It has methods to:
+
+
get item count
+
get a value for given item
+
register as listener for changes in data count and data values
+
+But this is a DSF interface, and all methods must be called on the
+service's dispatch thread. For this reason, the DataProvider interface returns
+an instance of DsfExecutor,
+which must be used with the interface.
+
Slow Data Provider
+
The data provider is actually implemented as a thread which is an
+inner class of SlowDataProvider
+service. The provider thread
+communicates with the service by reading Request objects from a shared
+queue, and by posting Runnable objects directly to the DsfExecutor but
+with a simulated transmission delay. Separately, an additional
+flag is also used to control the shutdown of the provider thread.
+To simulate a real back end, the data provider randomly invalidates a
+set of items and notifies the listeners to update themselves. It
+also periodically invalidates the whole table and forces the clients to
+requery all items.
+
Data and Control Flow
+
+This can be described in following steps:
+
+
The table viewer requests data for an item at a given index (SlowDataProviderContentProvider.updateElement).
+
+
The table viewer's content provider executes a Runnable in the DSF
+dispatch thread and calls the data provider interface (SlowDataProviderContentProvider.queryItemData).
+
Data provider service creates a Request object, and files it in a
+queue (SlowDataProvider.getItem).
+
Data provider thread de-queues the Request object and acts on it,
+calculating the value (ProviderThread.processItemRequest).
+
Data provider thread schedules the calculation result to be
+posted with DSF executor (SlowDataProvider.java:185).
+
The RequestMonitor callback sets the result data in the table
+viewer (SlowDataProviderContentProvider.java:167).
+
+
+
Running the example and full sources
+This example is implemented in the org.eclipse.dd.examples.dsf
+plugin, in the org.eclipse.dd.examples.dsf.concurrent
+package.
+
+To run the example:
+
+
Build the test plugin (along with the org.eclipse.dsdp.DSF plugin)
+and launch the PDE.
+
+
Make sure to add the DSF
+Tests action set to your current perspective.
+
From the main menu, select DSF
+Tests -> Slow Data Provider.
+
A dialog will open and after a delay it will populate with data.
+
Scroll and resize dialog and observe the update behavior.
+
+
Initial Notes
+
+This example is supposed to be representative of a typical embedded
+debugger design problem. Embedded debuggers are often slow in
+retrieving and processing data, and can sometimes be accessed through a
+relatively slow data channel, such as serial port or JTAG
+connection. But as such, this basic example presents a couple
+of major usability problems
+
+
The data provider service interface mirrors the table's content
+provider interface, in that it has a method to retrieve a single piece
+of data at a time. The result of this is visible to the user as
+lines of data are filled in one-by-one in the table. However,
+most debugger back ends are in fact capable of retrieving data in
+batches and are much more efficient at it than retrieving data items
+one-by-one.
+
When scrolling quickly through the table, the requests are
+generated by the table viewer for items which are quickly scrolled out
+of view, but the service still queues them up and calculates them in
+the order they were received. As a result, it takes a very long
+time for the table to be populated with data at the location where the
+user is looking.
+
+
+These two problems are very common in creating UI for embedded
+debugging, and there are common patterns which can be used to solve
+these problems in DSF services.
+
Coalescing
+Coalescing many single-item requests into fewer multi-item requests is
+the surest way to improve performance in communication with a remote
+debugger, although it's not necessarily the simplest. There are
+two basic patterns in which coalescing is achieved:
+
+
The back end provides an interface for retrieving data in large
+chunks. So when the service implementation receives a request for
+a single item, it retrieves a whole chunk of data, returns the single
+item, and stores the rest of the data in a local cache.
+
The back end providers an interface for retrieving data in
+variable size chunks. When the service implementation receives a
+request for a single item, it buffers the request, and waits for other
+requests to come in. After a delay, the service clears the buffer
+and submits a request for the combined items to the data provider.
+
+In practice, a combination of the two patterns is needed, but for
+purpose of an example, we implemented the second pattern in the
+"Input-Coalescing Slow Data Provider" (InputCoalescingSlowDataProvider.java).
+
+
Input Buffer
+
The main feature of this pattern is a buffer for holding the
+requests before sending them to the data provider. In this
+example the user requests are buffered in two arrays: fGetItemIndexesBuffer and fGetItemRequestMonitorsBuffer. The
+DataProvider.getItem()
+implementation is changed as follows:
+
public void getItem(final int index, final DataRequestMonitor<String> rm) { // Schedule a buffer-servicing call, if one is needed. if (fGetItemRequestMonitorsBuffer.isEmpty()) { fExecutor.schedule( new Runnable() { public void run() { fileBufferedRequests(); }}, COALESCING_DELAY_TIME, TimeUnit.MILLISECONDS); }
// Add the call data to the buffer. // Note: it doesn't matter that the items were added to the buffer // after the buffer-servicing request was scheduled. This is because // the buffers are guaranteed not to be modified until this dispatch // cycle is over. fGetItemIndexesBuffer.add(index); fGetItemRequestMonitorsBuffer.add(rm); }
+And method that services the buffer looks like this:
+
public void fileBufferedRequests() { // Remove a number of getItem() calls from the buffer, and combine them // into a request. int numToCoalesce = Math.min(fGetItemIndexesBuffer.size(), COALESCING_COUNT_LIMIT); final ItemRequest request = new ItemRequest(new Integer[numToCoalesce], new DataRequestMonitor[numToCoalesce]); for (int i = 0; i < numToCoalesce; i++) { request.fIndexes[i] = fGetItemIndexesBuffer.remove(0); request.fDones[i] = fGetItemRequestMonitorsBuffer.remove(0); }
// Queue the coalesced request, with the appropriate transmission delay. fQueue.add(request);
// If there are still calls left in the buffer, execute another // buffer-servicing call, but without any delay. if (!fGetItemIndexesBuffer.isEmpty()) { fExecutor.execute(new Runnable() { public void run() { fileBufferedRequests(); }}); } }
+The most interesting feature of this implementation is the fact that
+there are no semaphores anywhere to control access to the input
+buffers. Even though the buffers are serviced with a delay and
+multiple clients can call the getItem()
+method, the use of a single
+dispatch thread prevents any race conditions that could corrupt the
+buffer data. In real-world implementations, the buffers and
+caches that need to be used are far more sophisticated with much more
+complicated logic, and this is where managing access to them using the
+dispatch thread is ever more important.
+
Cancellability
+
Table Viewer
+
+Unlike coalescing, which can be implemented entirely within the
+service, cancellability requires that the client be modified as well
+to take advantage of this capability. For the table viewer
+content provider, this means that additional features have to be
+added. In CancellingSlowDataProviderContentProvider.java
+ILazyContentProvider.updateElement()
+was changes as follows:
+
public void updateElement(final int index) { assert fTableViewer != null; if (fDataProvider == null) return;
// Calculate the visible index range. final int topIdx = fTableViewer.getTable().getTopIndex(); final int botIdx = topIdx + getVisibleItemCount(topIdx);
fCancelCallsPending.incrementAndGet(); fDataProvider.getExecutor().execute( new Runnable() { public void run() { // Must check again, in case disposed while redispatching. if (fDataProvider == null || fTableViewer.getTable().isDisposed()) return; if (index >= topIdx && index <= botIdx) { queryItemData(index); } cancelStaleRequests(topIdx, botIdx); }}); }
+Now the client keeps track of the requests it made to the service in fItemDataDones, and above, cancelStaleRequests() iterates
+through all the outstanding requests and cancels the ones that are no
+longer in the visible range.
+
Data Provider Service
+
+
The data provider implementation
+(CancellableInputCoalescingSlowDataProvider.java),
+builds on top of the
+coalescing data provider. To make the canceling feature useful,
+the data provider service has to limit the size of the request
+queue. This is because in this example which simulates
+communication with a target and once requests are filed into the
+request
+queue, they cannot be canceled, just like a client can't cancel
+request once it sends them over a socket. So instead, if a flood
+of getItem()
+calls comes in, the service has to hold most of them in the coalescing
+buffer in case the client decides to cancel them. Therefore the
+fileBufferedRequests()
+method includes a simple check before servicing
+the buffer, and if the request queue is full, the buffer servicing call
+is delayed.
+
if (fQueue.size() >= REQUEST_QUEUE_SIZE_LIMIT) { if (fGetItemIndexesBuffer.isEmpty()) { fExecutor.schedule( new Runnable() { public void run() { fileBufferedRequests(); }}, REQUEST_BUFFER_FULL_RETRY_DELAY, TimeUnit.MILLISECONDS); } return; }
+Beyond this change, the only other significant change is that before
+the requests are queued, they are checked for cancellation.
+
Final Notes
+
+The example given here is fairly simplistic, and chances are that the
+same example could be implemented using semaphores and free threading
+with perhaps fewer lines of code. But what we have found is that
+as the problem gets bigger, the amount of
+features in the data provider increases, the state of the
+communication protocol gets more complicated, and the number of modules
+needed in the service layer increases, using free threading and
+semaphores does not safely scale. Using a dispatch thread for
+synchronization certainly doesn't make the inherent problems of the
+system less complicated, but it does help eliminate the race conditions
+and deadlocks from the overall system.
+
Coalescing and Cancellability are both optimizations. Neither
+of these optimizations affected the original interface of the service,
+and one of them only needed a service-side modification. But as
+with all optimizations, it is often better to first make sure that the
+whole system is working correctly and then add optimizations where they
+can make the biggest difference in user experience.
+
The above examples of optimizations can take many forms, and as
+mentioned with coalescing, caching data that is retrieved from the data
+provider is the most common form of data coalescing. For
+cancellation, many services in DSF build on top of other services,
+which means that even a low-level service can cause a higher
+level service to retrieve data, while another event might cause it to
+cancel those requests. The perfect example of this is a Variables
+service, which is responsible for calculating the value of expressions
+shown in the Variables view. The Variables service reacts to the
+Run Control service, which issues a suspended event and then requests a
+set of variables to be evaluated by the debugger back end. But as
+soon as a resumed event is issued by Run Control, the Variables service
+needs to cancel the pending evaluation requests.
+
+
+
+
+
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserAction.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserAction.java
new file mode 100644
index 00000000000..76b5d5cee71
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserAction.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.actions.ActionDelegate;
+
+/**
+ * Action that opens the File Browser example dialog.
+ */
+public class FileBrowserAction extends ActionDelegate
+ implements IWorkbenchWindowActionDelegate
+{
+ private IWorkbenchWindow fWindow;
+
+ @Override
+ public void run(IAction action) {
+ if (fWindow != null) {
+ // Create the dialog and open it.
+ Dialog dialog = new FileBrowserDialog(fWindow.getShell());
+ dialog.open();
+ }
+ }
+
+ public void init(IWorkbenchWindow window) {
+ fWindow = window;
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserDialog.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserDialog.java
new file mode 100644
index 00000000000..2cd32389d60
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserDialog.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * File Browser example dialog. It hold a tree viewer that displays
+ * file system contents and a text box for entering a file path to be
+ * shown in the tree.
+ */
+@SuppressWarnings("restriction")
+public class FileBrowserDialog extends Dialog {
+
+ /**
+ * Tree viewer for showing the filesystem contents.
+ */
+ private TreeModelViewer fViewer;
+
+ /**
+ * The model adapter for the tree viewer.
+ */
+ private FileBrowserModelAdapter fModelAdapter;
+
+ /**
+ * Flag used to disable text-box changed events, when the text
+ * box is updated due to selection change in tree.
+ */
+ private boolean fDisableTextChangeNotifications = false;
+
+ public FileBrowserDialog(Shell parent) {
+ super(parent);
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite area = (Composite) super.createDialogArea(parent);
+ IPresentationContext presentationContext = new PresentationContext("org.eclipse.dd.examples.dsf.filebrowser"); //$NON-NLS-1$
+
+ fViewer = new TreeModelViewer(area, SWT.VIRTUAL, presentationContext);
+ fViewer.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
+
+ fModelAdapter = new FileBrowserModelAdapter(presentationContext);
+ fViewer.setInput(fModelAdapter.getVMProvider().getViewerInputObject());
+
+ final Text text = new Text(area, SWT.SINGLE | SWT.BORDER);
+ text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ fViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ /*
+ * Update the file name in the text control, to match the
+ * selection in the tree. Do this only if the user is not
+ * actively typing in the text field (test if text has focus).
+ */
+ if (!text.isFocusControl() &&
+ event.getSelection() instanceof IStructuredSelection &&
+ ((IStructuredSelection)event.getSelection()).getFirstElement() instanceof FileVMContext)
+ {
+ FileVMContext fileVmc = (FileVMContext)((IStructuredSelection)event.getSelection()).getFirstElement();
+
+ fDisableTextChangeNotifications = true;
+ text.setText(fileVmc.getFile().getAbsolutePath());
+ fDisableTextChangeNotifications = false;
+ }
+ }
+ });
+
+ text.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ if (!fDisableTextChangeNotifications) {
+ fModelAdapter.getVMProvider().selectionTextChanged(text.getText());
+ }
+ }
+ });
+
+ return area;
+ }
+
+ @Override
+ public boolean close() {
+ if (super.close()) {
+ fModelAdapter.dispose();
+ fModelAdapter = null;
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserModelAdapter.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserModelAdapter.java
new file mode 100644
index 00000000000..d77d489a7be
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserModelAdapter.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import org.eclipse.dd.dsf.concurrent.ThreadSafe;
+import org.eclipse.dd.dsf.service.DsfSession;
+import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMAdapter;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMProvider;
+import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
+
+/**
+ * This is the adapter that implements the flexible hierarchy viewer interfaces
+ * for providing content, labels, and event proxy-ing for the viewer. This
+ * adapter is registered with the DSF Session object, and is returned by the
+ * IDMContext.getAdapter() and IVMContext.getAdapter() methods,
+ * which both call {@link DsfSession#getModelAdapter(Class)}.
+ *
+ * The adapter implementation for this excercise is hard-coded to provide
+ * contents for only one view. In turn the view contens are determined using
+ * the configurable ViewModelProvider. For demonstration purposes, this model
+ * adapter has two different layout configurations that can be used. These
+ * layout configurations can be set by calling the {@link #setViewLayout} method.
+ *
+ * This class is primarily accessed by the flexible hierarchy viewer from a
+ * non-executor thread. So the class is thread-safe, except for a view methods
+ * which must be called on the executor thread.
+ *
+ * @see AbstractDMVMProvider
+ */
+@SuppressWarnings("restriction")
+@ThreadSafe
+public class FileBrowserModelAdapter extends AbstractVMAdapter
+{
+ FileBrowserVMProvider fViewModelProvider;
+
+ @Override
+ protected IVMProvider createViewModelProvider(IPresentationContext context) {
+ /*
+ * In this example there is only one viewer, so there is only one
+ * VMProvider.
+ */
+ return fViewModelProvider;
+ }
+
+ public FileBrowserModelAdapter(IPresentationContext presentationContext) {
+ super();
+ fViewModelProvider = new FileBrowserVMProvider(this, presentationContext);
+ }
+
+ FileBrowserVMProvider getVMProvider() {
+ return fViewModelProvider;
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserVMProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserVMProvider.java
new file mode 100644
index 00000000000..b8395919d37
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileBrowserVMProvider.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMAdapter;
+import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMProvider;
+import org.eclipse.dd.dsf.ui.viewmodel.IRootVMNode;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMNode;
+import org.eclipse.dd.dsf.ui.viewmodel.RootVMNode;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
+
+/**
+ *
+ */
+@SuppressWarnings("restriction")
+public class FileBrowserVMProvider extends AbstractVMProvider
+{
+ /**
+ * The object to be set to the viewer that shows contents supplied by this provider.
+ * @see org.eclipse.jface.viewers.TreeViewer#setInput(Object)
+ */
+ private final IAdaptable fViewerInputObject =
+ new IAdaptable() {
+ /**
+ * The input object provides the viewer access to the viewer model adapter.
+ */
+ @SuppressWarnings("unchecked")
+ public Object getAdapter(Class adapter) {
+ if ( adapter.isInstance(getVMAdapter()) ) {
+ return getVMAdapter();
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "File Browser Viewer Input"; //$NON-NLS-1$
+ }
+ };
+
+ /**
+ * Constructor creates and configures the layout nodes to display file
+ * system contents.
+ * @param adapter The viewer model adapter that this provider is registered with.
+ * @param presentationContext The presentation context that this provider is
+ * generating contents for.
+ */
+ public FileBrowserVMProvider(AbstractVMAdapter adapter, IPresentationContext presentationContext) {
+ super(adapter, presentationContext);
+
+ IRootVMNode root = new RootVMNode(this);
+ IVMNode fileSystemRoots = new FilesystemRootsVMNode(this);
+ addChildNodes(root, new IVMNode[] { fileSystemRoots });
+ IVMNode files = new FileVMNode(this);
+ addChildNodes(fileSystemRoots, new IVMNode[] { files });
+ setRootNode(root);
+ }
+
+ /**
+ * Returns the input object to be set to the viewer that shows contents
+ * supplied by this provider.
+ */
+ public Object getViewerInputObject() {
+ return fViewerInputObject;
+ }
+
+ /**
+ * Event handler for file selection text changes in the dialog.
+ * @param text New text entered in file selection text box.
+ */
+ void selectionTextChanged(final String text) {
+ if (isDisposed()) return;
+
+ // We're in the UI thread. Re-dispach to VM Adapter executor thread
+ // and then call root layout node.
+ try {
+ getExecutor().execute(new Runnable() {
+ public void run() {
+ if (isDisposed()) return;
+ handleEvent(text);
+ }});
+ } catch (RejectedExecutionException e) {
+ // Ignore. This exception could be thrown if the provider is being
+ // shut down.
+ }
+ }
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileVMContext.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileVMContext.java
new file mode 100644
index 00000000000..a4da73f10eb
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileVMContext.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import java.io.File;
+
+import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMContext;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMAdapter;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMNode;
+
+class FileVMContext extends AbstractVMContext {
+ private File fFile;
+ FileVMContext(IVMAdapter adapter, IVMNode layoutNode, File file) {
+ super(adapter, layoutNode);
+ fFile = file;
+ }
+
+ File getFile() { return fFile; }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof FileVMContext && ((FileVMContext)obj).getFile().equals(fFile);
+ }
+
+ @Override
+ public int hashCode() {
+ return fFile.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileVMNode.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileVMNode.java
new file mode 100644
index 00000000000..e9699c78a9e
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FileVMNode.java
@@ -0,0 +1,308 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.dd.dsf.internal.ui.DsfUIPlugin;
+import org.eclipse.dd.dsf.service.IDsfService;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMContext;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMNode;
+import org.eclipse.dd.dsf.ui.viewmodel.IVMProvider;
+import org.eclipse.dd.dsf.ui.viewmodel.VMDelta;
+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.IElementLabelProvider;
+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.IModelDelta;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
+
+
+/**
+ * File view model node which returns file elements that are found in the directory
+ * specified by the parent element. The child nodes of this node are fixed to
+ * reference this element, and therefore this node will recursively populate
+ * the contents of the tree reflecting the underlying filesystem directories.
+ *
+ * Note: this node does NOT sub-class the {@link org.eclipse.dd.dsf.ui.viewmodel.AbstractVMNode}
+ */
+@SuppressWarnings("restriction")
+class FileVMNode
+ implements IElementLabelProvider, IVMNode
+{
+ /**
+ * Reference to the viewer model provider. It's mainly used to access the
+ * viewer model adapter and its executor.
+ */
+ private final FileBrowserVMProvider fProvider;
+
+ public FileVMNode(FileBrowserVMProvider provider) {
+ fProvider = provider;
+ }
+
+ public void dispose() {
+ // All resources garbage collected.
+ }
+
+ public void setChildNodes(IVMNode[] childNodes) {
+ throw new UnsupportedOperationException("This node does not support children."); //$NON-NLS-1$
+ }
+
+ /**
+ * List of child nodes containing only a reference to this.
+ */
+ private final IVMNode[] fChildNodes = { this };
+
+ public IVMNode[] getChildNodes() {
+ return fChildNodes;
+ }
+
+ public void update(final IHasChildrenUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ for (IHasChildrenUpdate update : updates) {
+ /*
+ * Do not retrieve directory contents just to mark the plus
+ * sign in the tree. If it's a directory, just assume that
+ * it has children.
+ */
+ FileVMContext vmc = (FileVMContext)update.getElement();
+ update.setHasChilren(vmc.getFile().isDirectory());
+ update.done();
+ }
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ public void update(final IChildrenCountUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ for (IChildrenCountUpdate update : updates) {
+ update.setChildCount(getFiles(update).length);
+ update.done();
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ public void update(final IChildrenUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ for (IChildrenUpdate update : updates) {
+ File[] files = getFiles(update);
+ int offset = update.getOffset() != -1 ? update.getOffset() : 0;
+ int length = update.getLength() != -1 ? update.getLength() : files.length;
+ for (int i = offset; (i < files.length) && (i < (offset + length)); i++) {
+ update.setChild(new FileVMContext(fProvider.getVMAdapter(), FileVMNode.this, files[i]), i);
+ }
+ update.done();
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ public void update(final ILabelUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ for (ILabelUpdate update : updates) {
+ update.setLabel(getLabel((FileVMContext)update.getElement()), 0);
+ update.done();
+ }
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ private static final File[] EMPTY_FILE_LIST = new File[0];
+
+ /**
+ * Retrieves the list of files for this node. The list of files is based
+ * on the parent element in the tree, which must be of type FileVMC.
+ *
+ * @param update Update object containing the path (and the parent element)
+ * in the tree viewer.
+ * @return List of files contained in the directory specified in the
+ * update object. An empty list if the parent element is not a directory.
+ * @throws ClassCastException If the parent element contained in the update
+ * is NOT of type FileVMC.
+ */
+ private File[] getFiles(IViewerUpdate update) {
+ FileVMContext vmc = (FileVMContext)update.getElement();
+ File[] files = vmc.getFile().listFiles();
+ return files != null ? files : EMPTY_FILE_LIST;
+ }
+
+ /**
+ * Returs the text label to show in the tree for given element.
+ */
+ private String getLabel(FileVMContext vmc) {
+ return vmc.getFile().getName();
+ }
+
+ public void getContextsForEvent(VMDelta parentDelta, Object event, DataRequestMonitor rm) {
+ rm.setStatus(new Status(IStatus.OK, DsfUIPlugin.PLUGIN_ID, IDsfService.NOT_SUPPORTED, "", null)); //$NON-NLS-1$
+ rm.done();
+ }
+
+ public int getDeltaFlags(Object e) {
+ /*
+ * @see buildDelta()
+ */
+ int retVal = IModelDelta.NO_CHANGE;
+ if (e instanceof String) {
+ retVal |= IModelDelta.SELECT | IModelDelta.EXPAND;
+ }
+
+ return retVal;
+ }
+
+ public void buildDelta(final Object event, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {
+ /*
+ * The FileLayoutNode is recursive, with itself as the only child. In this
+ * method the delta is calculated for a full path VMContext elements, and the
+ * implementation of this method is not recursive.
+ */
+ if (event instanceof String) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ /*
+ * Requirements for a selection event to be issued is that the file exist, and
+ * that the parentDelta contain a FileVMC of a parent directory as its element.
+ *
+ * The test for first the former requirement could be performed inside getDeltaFlags()
+ * but getDeltaFlags() is synchronous, so it is better to perform this test here using
+ * a background thread (job).
+ *
+ * The latter is requirement is needed because this node does not have the algorithm
+ * calculate the complete list of root nodes. That algorithm is implemented inside the
+ * {@link FileSystemRootsLayoutNode#updateElements} method.
+ */
+
+ final File eventFile = new File((String)event);
+ File parentFile = null;
+ if (parentDelta.getElement() instanceof FileVMContext) {
+ parentFile = ((FileVMContext)parentDelta.getElement()).getFile();
+ }
+
+ // The file has to exist in order for us to be able to select
+ // it in the tree.
+ if (eventFile.exists() && parentFile != null) {
+ // Create a list containing all files in path
+ List filePath = new LinkedList();
+ for (File file = eventFile; file != null && !file.equals(parentFile); file = file.getParentFile()) {
+ filePath.add(0, file);
+ }
+
+ if (filePath.size() != 0) {
+ // Build the delta for all files in path.
+ ModelDelta delta = parentDelta;
+ File[] allFilesInDirectory = parentFile.listFiles();
+ for (File pathSegment : filePath) {
+ // All files in path should be directories, and should therefore
+ // have a valid list of elements.
+ assert allFilesInDirectory != null;
+
+ File[] pathSegmentDirectoryFiles = pathSegment.listFiles();
+ delta = delta.addNode(
+ new FileVMContext(fProvider.getVMAdapter(), FileVMNode.this, pathSegment),
+ nodeOffset + Arrays.asList(allFilesInDirectory).indexOf(pathSegment),
+ IModelDelta.NO_CHANGE,
+ pathSegmentDirectoryFiles != null ? pathSegmentDirectoryFiles.length : 0);
+ allFilesInDirectory = pathSegmentDirectoryFiles;
+ }
+
+ // The last file in path gets the EXPAND | SELECT flags.
+ delta.setFlags(delta.getFlags() | IModelDelta.SELECT | IModelDelta.EXPAND);
+ }
+ }
+
+ // Invoke the request monitor.
+
+ requestMonitor.done();
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ } else {
+ requestMonitor.done();
+ }
+ }
+
+ /**
+ * Override the behavior which checks for delta flags of all the child nodes,
+ * because we would get stuck in a recursive loop. Instead call only the child
+ * nodes which are not us.
+ */
+ protected Map getChildNodesWithDeltas(Object e) {
+ Map nodes = new HashMap();
+ for (final IVMNode childNode : getChildNodes()) {
+ int delta = childNode.getDeltaFlags(e);
+ if (delta != IModelDelta.NO_CHANGE) {
+ nodes.put(childNode, delta);
+ }
+ }
+ return nodes;
+ }
+
+ public IVMProvider getVMProvider() {
+ return fProvider;
+ }
+
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FilesystemRootsVMNode.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FilesystemRootsVMNode.java
new file mode 100644
index 00000000000..3e64684267f
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/FilesystemRootsVMNode.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.filebrowser;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.dd.dsf.concurrent.RequestMonitor;
+import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMNode;
+import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMProvider;
+import org.eclipse.dd.dsf.ui.viewmodel.VMDelta;
+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.IElementLabelProvider;
+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.IModelDelta;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
+
+
+/**
+ * Viewer model node that populates the filesystem root elements.
+ */
+@SuppressWarnings("restriction")
+class FilesystemRootsVMNode extends AbstractVMNode
+ implements IElementLabelProvider
+{
+ public FilesystemRootsVMNode(AbstractVMProvider provider) {
+ super(provider);
+ }
+
+ public void update(final IChildrenUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ File[] files = File.listRoots();
+ for (IChildrenUpdate update : updates) {
+ int offset = update.getOffset() != -1 ? update.getOffset() : 0;
+ int length = update.getLength() != -1 ? update.getLength() : files.length;
+ for (int i = offset; (i < files.length) && (i < (offset + length)); i++) {
+ update.setChild(new FileVMContext(getVMProvider().getVMAdapter(), FilesystemRootsVMNode.this, files[i]), i);
+ }
+ update.done();
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ public void update(final IHasChildrenUpdate[] updates) {
+ for (IHasChildrenUpdate update : updates) {
+ /*
+ * Assume that all filesystem roots have children. If user attempts
+ * to expand an empty directory, the plus sign will be removed
+ * from the element.
+ */
+ update.setHasChilren(true);
+ update.done();
+ }
+ }
+
+ public void update(final IChildrenCountUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ for (IChildrenCountUpdate update : updates) {
+ if (!checkUpdate(update)) continue;
+ update.setChildCount(File.listRoots().length);
+ update.done();
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ public void update(final ILabelUpdate[] updates) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ for (ILabelUpdate update : updates) {
+ update.setLabel(getLabel((FileVMContext)update.getElement()), 0);
+ update.done();
+ }
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+
+ /**
+ * Returs the text label to show in the tree for given element. Filesystem
+ * roots return an empty string for call to File.getName(), use the abolute path
+ * string instead.
+ */
+ private String getLabel(FileVMContext vmc) {
+ return vmc.getFile().getAbsolutePath();
+ }
+
+ public int getDeltaFlags(Object e) {
+ /*
+ * @see buildDelta()
+ */
+ int retVal = IModelDelta.NO_CHANGE;
+ if (e instanceof String) {
+ retVal |= IModelDelta.SELECT | IModelDelta.EXPAND;
+ }
+
+ return retVal;
+ }
+
+ public void buildDelta(final Object event, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {
+ if (event instanceof String) {
+ new Job("") { //$NON-NLS-1$
+ {
+ setSystem(true);
+ setPriority(INTERACTIVE);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ final File eventFile = new File((String)event);
+
+ if (eventFile.exists()) {
+ // Create a list containing all files in path of the file from the event
+ List filePath = new LinkedList();
+ for (File file = eventFile; file != null; file = file.getParentFile()) {
+ filePath.add(0, file);
+ }
+ File eventRoot = filePath.get(0);
+
+ // Get the index of the file in list of filesystem roots.
+ File[] roots = File.listRoots();
+
+ int index = 0;
+ for (; index < roots.length; index++) {
+ if (eventRoot.equals(roots[index])) break;
+ }
+
+ // Check if the specified file is not one of the roots.
+ if (index < roots.length) {
+ ModelDelta delta = parentDelta.addNode(
+ new FileVMContext(getVMProvider().getVMAdapter(), FilesystemRootsVMNode.this, eventRoot),
+ index, IModelDelta.NO_CHANGE);
+
+ if (eventFile.equals(eventRoot)) {
+ // The event is for the root node. Select it and extend parent node.
+ delta.setFlags(delta.getFlags() | IModelDelta.SELECT | IModelDelta.EXPAND);
+ }
+ }
+ }
+
+ // Invoke the request monitor.
+ requestMonitor.done();
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ } else {
+ requestMonitor.done();
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/package.html b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/package.html
new file mode 100644
index 00000000000..c9b2441c5d5
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/filebrowser/package.html
@@ -0,0 +1,127 @@
+
+
+
+
+ DSF Filesystem Browser Example
+
+
+
DSF Filesystem Browser Example
+
Goals
+This example demonstrates an implementation of a viewer model with a
+layout node that has itself as a child. Such layout nodes are
+needed to represents elements which themselves have a natural tree
+structures. This example uses filesystem folders as the
+tree-structured data, which is retrieved directly from the java.io.File
+class. This example also demonstrates a viewer model
+implementation which does not retrieve data using DSF services and
+associated data model interfaces.
+
Design
+Model Adapter Hookup
+A flexible-hierarchy tree viewer {@link
+org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer}
+is created within a model dialog. Corresponding {@link
+FileBrowserModelAdapter} and {@link FileBrowserVMProvider} classes are
+instanciated, and the root element object created by
+FileBrowserVMProvider is set as input to the tree viewer. From
+there FileBrowserModelAdapter is returned as the {@link
+IElementContentProvier} and {@link IModelProxyFactory} for all elements
+in the tree.
+
+
Layout Nodes
+There are three layout nodes:
+
+
+
{@link FileBrowserVMProvider.VMRootLayoutNode} is just a root
+node, which generates the input element for the viewer.
+
{@link FilesystemRootsLayoutNode} retrieves the roots of the
+filesystem hierarchy ("C:\", "D:\", etc on Windows).
+
+
{@link FileLayoutNode} is a child of FilesystemRootsLayoutNode and
+it recursively retrieves all folders and files under the given parent
+file element. This layout node does not allow any children nodes
+to be added to it, and it returns only itself as a child node (through
+a call to IVMLayoutNode.getChildLayoutNodes).
+
+
+Both FilesystemRootsLayoutNode
+and FileLayoutNode create
+elements of the same type: {@link FileVMContext}. Additionally,
+when populating elements in the tree, the FileLayoutNode requires that a FileVMContext element be the
+parent element in order to be able to retrieve its children.
+
+
+Event Handling/Generating
+Model Deltas
+The view model responds to events generated by a text box in the
+dialog, where the user can type in a filesystem path. If the
+entered path resolves to a file on the filesystem, the view model
+generates a delta to select and reveal the given file in the
+tree. The two file layout nodes handle generating the delta in
+different ways:
+
+
FilesystemRootsLayoutNode
+is a standard layout node.
+
+
+
In the event handler implementation {@link
+org.eclipse.dd.dsf.ui.viewermodel.IVMLayoutNode#buildDelta}, the user
+specified file-path is compared to the list of file-system roots.
+
+
+
If the user file-path contains one of the filesystem roots, a
+new delta node is added for that root and the child layout node is
+called to continue the delta processing.
+
+
If the user file-path points to one of the filesystem roots,
+the IModelDelta.SELECT
+and IModelDelta.EXPAND
+flags are also added to the delta so that the root will be selected in
+the viewer.
+
+
+
FileLayoutNode is
+the special case, because it is a recusrive node. This node does
+not call any child nodes to process the delta, instead it calculates
+the delta for all file elements in user file-path, starting at the
+parent element.
+
+
+
First the parent FileVMContext
+element is retrieved from the delta.
+
+
Then the user file-path is broken down into {@link
+java.io.File} objects representing each segment in the path, starting
+at the parent file element retrieved in step 1.
+
Then a delta node is added for each segment of the calculated
+path.
+
+
IModelDelta.SELECT
+and IModelDelta.EXPAND
+flags are added to the last delta.
+
+
+
+
How to use
+
+
Make sure that the DSF examples menu is visible in the perspective
+
+
Go to Windows -> Customize Perspective...
+
Select Commands tab
+
Check the "DSF Examples" in the "Available command groups"
+table.
+
+
Open the dialog by selecting DSF Examples->Open File Browser
+Dialog menu item.
+
Expand the items in the tree to see filesystem contents.
+
Select elements in the tree, to fill in text box with selected
+file's path.
+
Type in a file path in text box and have the tree expand to the
+specified element.
+
+
+
+
diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmCellModifier.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmCellModifier.java
new file mode 100644
index 00000000000..e0ae7d54606
--- /dev/null
+++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmCellModifier.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.dd.examples.dsf.timers;
+
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.dd.dsf.concurrent.DataRequestMonitor;
+import org.eclipse.dd.dsf.concurrent.Query;
+import org.eclipse.dd.dsf.concurrent.ThreadSafe;
+import org.eclipse.dd.dsf.concurrent.ThreadSafeAndProhibitedFromDsfExecutor;
+import org.eclipse.dd.dsf.service.DsfServices;
+import org.eclipse.dd.dsf.service.DsfSession;
+import org.eclipse.dd.dsf.service.IDsfService;
+import org.eclipse.dd.examples.dsf.DsfExamplesPlugin;
+import org.eclipse.dd.examples.dsf.timers.AlarmService.AlarmDMC;
+import org.eclipse.dd.examples.dsf.timers.AlarmService.AlarmData;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ICellModifier;
+import org.eclipse.swt.widgets.Shell;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ *
+ */
+@ThreadSafeAndProhibitedFromDsfExecutor("")
+public class AlarmCellModifier implements ICellModifier {
+
+ private final DsfSession fSession;
+
+ /**
+ * Need to use the OSGi service tracker here (instead of DsfServiceTracker),
+ * because we're accessing it in non-dispatch thread. DsfServiceTracker is not
+ * thread-safe.
+ */
+ @ThreadSafe
+ private ServiceTracker fServiceTracker;
+
+ /**
+ * Constructor for the modifier requires a valid DSF session in order to
+ * initialize the service tracker.
+ * @param session DSF session this modifier will use.
+ */
+ public AlarmCellModifier(DsfSession session) {
+ fSession = session;
+ }
+
+ public boolean canModify(Object element, String property) {
+ return TimersViewColumnPresentation.COL_VALUE.equals(property) && getAlarmDMC(element) != null;
+ }
+
+ public Object getValue(Object element, String property) {
+ if (!TimersViewColumnPresentation.COL_VALUE.equals(property)) return ""; //$NON-NLS-1$
+
+ // Get the DMC and the session. If element is not an alarm DMC, or
+ // session is stale, then bail out.
+ AlarmDMC dmc = getAlarmDMC(element);
+ if (dmc == null) return ""; //$NON-NLS-1$
+ DsfSession session = DsfSession.getSession(dmc.getSessionId());
+ if (session == null) return ""; //$NON-NLS-1$
+
+ /*
+ * Create the query to request the value from service.
+ * Note: no need to guard against RejectedExecutionException, because
+ * DsfSession.getSession() above would only return an active session.
+ */
+ GetValueQuery query = new GetValueQuery(dmc);
+ session.getExecutor().execute(query);
+ try {
+ return query.get().toString();
+ } catch (InterruptedException e) {
+ assert false;
+ return ""; //$NON-NLS-1$
+ } catch (ExecutionException e) {
+ return ""; //$NON-NLS-1$
+ }
+ }
+
+
+ public void modify(Object element, String property, Object value) {
+ if (!TimersViewColumnPresentation.COL_VALUE.equals(property)) return;
+
+ AlarmDMC dmc = getAlarmDMC(element);
+ if (dmc == null) return;
+ DsfSession session = DsfSession.getSession(dmc.getSessionId());
+ if (session == null) return;
+
+ // Shell is used in displaying error dialogs.
+ Shell shell = getShell();
+ if (shell == null) return;
+
+ Integer intValue = null;
+ if (value instanceof String) {
+ try {
+ intValue = new Integer(((String)value).trim());
+ } catch (NumberFormatException e) {
+ MessageDialog.openError(shell, "Invalid Value", "Please enter a positive integer"); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ if (intValue.intValue() <= 0) {
+ MessageDialog.openError(shell, "Invalid Value", "Please enter a positive integer"); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ }
+
+ /*
+ * Create the query to write the value to the service.
+ * Note: no need to guard against RejectedExecutionException, because
+ * DsfSession.getSession() above would only return an active session.
+ */
+ SetValueQuery query = new SetValueQuery(dmc, intValue);
+
+ session.getExecutor().execute(query);
+
+ try {
+ // Return value is irrelevant, any error would come through with an exception.
+ query.get().toString();
+ } catch (InterruptedException e) {
+ assert false;
+ } catch (ExecutionException e) {
+ // View must be shutting down, no need to show erro dialog.
+ }
+ }
+
+ /**
+ * Need to dispose the cell modifier property because of the service
+ * tracker.
+ */
+ @ThreadSafe
+ public synchronized void dispose() {
+ if (fServiceTracker != null) {
+ fServiceTracker.close();
+ }
+ }
+
+ private Shell getShell() {
+ if (DsfExamplesPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow() != null) {
+ return DsfExamplesPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
+ }
+ return null;
+ }
+
+ private AlarmDMC getAlarmDMC(Object element) {
+ if (element instanceof IAdaptable) {
+ return (AlarmDMC)((IAdaptable)element).getAdapter(AlarmDMC.class);
+ }
+ return null;
+ }
+
+ @ThreadSafe
+ private synchronized AlarmService getService(AlarmDMC dmc) {
+ String serviceId = DsfServices.createServiceFilter( AlarmService.class, fSession.getId() );
+ if (fServiceTracker == null) {
+ try {
+ fServiceTracker = new ServiceTracker(
+ DsfExamplesPlugin.getBundleContext(),
+ DsfExamplesPlugin.getBundleContext().createFilter(serviceId),
+ null);
+ fServiceTracker.open();
+ } catch (InvalidSyntaxException e) {
+ return null;
+ }
+ }
+ return (AlarmService)fServiceTracker.getService();
+ }
+
+ private class GetValueQuery extends Query {
+
+ final AlarmDMC fDmc;
+
+ private GetValueQuery(AlarmDMC dmc) {
+ super();
+ fDmc = dmc;
+ }
+
+ @Override
+ protected void execute(final DataRequestMonitor rm) {
+ /*
+ * Guard against the session being disposed. If session is disposed
+ * it could mean that the executor is shut-down, which in turn
+ * could mean that we can't execute the "done" argument.
+ * In that case, cancel to notify waiting thread.
+ */
+ final DsfSession session = DsfSession.getSession(fDmc.getSessionId());
+ if (session == null) {
+ cancel(false);
+ return;
+ }
+
+ AlarmService service = getService(fDmc);
+ if (service == null) {
+ rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfService.INVALID_STATE,
+ "Service not available", null)); //$NON-NLS-1$
+ return;
+ }
+
+ service.getAlarmData(fDmc, new DataRequestMonitor(session.getExecutor(), rm) {
+ @Override
+ protected void handleCompleted() {
+ // We're in another dispatch, so we must guard against executor shutdown again.
+ if (DsfSession.isSessionActive(session.getId())) {
+ super.handleCompleted();
+ }
+ }
+
+ @Override
+ protected void handleOK() {
+ rm.setData(getData().getTriggeringValue());
+ rm.done();
+ }
+ });
+ }
+ }
+
+ private class SetValueQuery extends Query