diff --git a/plugins/org.eclipse.dd.examples.dsf/.classpath b/plugins/org.eclipse.dd.examples.dsf/.classpath new file mode 100644 index 00000000000..304e86186aa --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/org.eclipse.dd.examples.dsf/.project b/plugins/org.eclipse.dd.examples.dsf/.project new file mode 100644 index 00000000000..5bc60355c8c --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/.project @@ -0,0 +1,28 @@ + + + org.eclipse.dd.examples.dsf + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/plugins/org.eclipse.dd.examples.dsf/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.dd.examples.dsf/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..3d7e8d5439f --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,65 @@ +#Thu Jun 07 11:07:55 PDT 2007 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +org.eclipse.jdt.core.compiler.problem.nullReference=ignore +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/plugins/org.eclipse.dd.examples.dsf/META-INF/MANIFEST.MF b/plugins/org.eclipse.dd.examples.dsf/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..68022d0409e --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Debug Services Framework Examples +Bundle-Vendor: Eclipse.org +Bundle-SymbolicName: org.eclipse.dd.examples.dsf;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.dd.examples.dsf.DsfExamplesPlugin +Bundle-Localization: plugin +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + org.eclipse.debug.core, + org.eclipse.debug.ui, + org.eclipse.dd.dsf, + org.eclipse.ui, + org.eclipse.dd.dsf.ui + +Eclipse-LazyStart: true +Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/plugins/org.eclipse.dd.examples.dsf/about.html b/plugins/org.eclipse.dd.examples.dsf/about.html new file mode 100644 index 00000000000..cb740ae8bc8 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/about.html @@ -0,0 +1,24 @@ + + + + +About +

About This Content

+ +

June 5, 2007

+

License

+ +

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 + + +

Version +1.0
+Pawel Piech
+© 2006, Wind River Systems.  Release +under EPL version 1.0.

+

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:

+
    +
  1. 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.
  2. +
  3. Next the content provider tells the viewer how many elements +there are, by calling TableViewer.setItemCount().
  4. +
  5. 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().
  6. +
  7. After calculating the value, the content provider tells the table +what the value is, by calling TableViewer.replace().
  8. +
  9. If the data ever changes, the content provider tells the table to +rerequest the data, by calling TableViewer.clear().
  10. +
+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:

+ +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:
+
    +
  1. The table viewer requests data for an item at a given index (SlowDataProviderContentProvider.updateElement).
    +
  2. +
  3. The table viewer's content provider executes a Runnable in the DSF +dispatch thread and calls the data provider interface (SlowDataProviderContentProvider.queryItemData).
  4. +
  5. Data provider service creates a Request object, and files it in a +queue (SlowDataProvider.getItem).
  6. +
  7. Data provider thread de-queues the Request object and acts on it, +calculating the value (ProviderThread.processItemRequest).
  8. +
  9. Data provider thread schedules the calculation result to be +posted with DSF executor (SlowDataProvider.java:185).
  10. +
  11. The RequestMonitor callback sets the result data in the table +viewer (SlowDataProviderContentProvider.java:167).
    +
  12. +
+

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:
+
    +
  1. Build the test plugin (along with the org.eclipse.dsdp.DSF plugin) +and launch the PDE. 
    +
  2. +
  3. Make sure to add the DSF +Tests action set to your current perspective.
  4. +
  5. From the main menu, select DSF +Tests -> Slow Data Provider.
  6. +
  7. A dialog will open and after a delay it will populate with data.
  8. +
  9. Scroll and resize dialog and observe the update behavior.
  10. +
+

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
+
    +
  1. 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.
  2. +
  3. 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. 
    +
  4. +
+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:
+
    +
  1. 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.
  2. +
  3. 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.
  4. +
+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:
+

+ +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:
+ +

How to use

+
    +
  1. Make sure that the DSF examples menu is visible in the perspective
  2. + +
  3. Open the dialog by selecting DSF Examples->Open File Browser +Dialog menu item.
  4. +
  5. Expand the items in the tree to see filesystem contents.
  6. +
  7. Select elements in the tree, to fill in text box with selected +file's path.
  8. +
  9. Type in a file path in text box and have the tree expand to the +specified element.
    +
  10. +
+ + 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 { + + AlarmDMC fDmc; + int fValue; + + SetValueQuery(AlarmDMC dmc, int value) { + super(); + fDmc = dmc; + fValue = value; + } + + @Override + protected void execute(final DataRequestMonitor rm) { + // Guard against terminated session + final DsfSession session = DsfSession.getSession(fDmc.getSessionId()); + if (session == null) { + cancel(false); + return; + } + + // Guard against a disposed service + 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$ + rm.done(); + return; + } + + // Finally set the value and return. + service.setAlarmValue(fDmc, fValue); + + // Return value is irrelevant. + rm.setData(new Object()); + rm.done(); + } + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmService.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmService.java new file mode 100644 index 00000000000..ec4ef5c0e6b --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmService.java @@ -0,0 +1,361 @@ +/******************************************************************************* + * 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.Hashtable; +import java.util.Map; +import java.util.TreeMap; + +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.RequestMonitor; +import org.eclipse.dd.dsf.datamodel.AbstractDMContext; +import org.eclipse.dd.dsf.datamodel.AbstractDMEvent; +import org.eclipse.dd.dsf.datamodel.DMContexts; +import org.eclipse.dd.dsf.datamodel.IDMContext; +import org.eclipse.dd.dsf.datamodel.IDMData; +import org.eclipse.dd.dsf.datamodel.IDMService; +import org.eclipse.dd.dsf.service.AbstractDsfService; +import org.eclipse.dd.dsf.service.DsfServiceEventHandler; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerDMC; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerData; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerTickEvent; +import org.osgi.framework.BundleContext; + +/** + * Alarm service tracks a set of alarm objects which are occacionally + * triggered by the timers from the TimerService. + *

+ * This service depends on the TimerService, so the TimerService has to be + * running before this service is initialized. However, the alarm objects + * themeselves do not depend on the timers, they can be listed, created, + * removed without any timers present. So a separate context object exists + * to track alarm status, which requires both an alarm and a timer in order + * to exist. + */ +public class AlarmService extends AbstractDsfService + implements IDMService +{ + /** + * Event indicating that the list of alarms is changed and the clients + * which display alarms should re-query this list. + */ + public class AlarmsChangedEvent extends AbstractDMEvent { + AlarmsChangedEvent() { super(fAlarmsContext); } + } + + /** + * Context representing an alarm tracked by this service. + */ + public static class AlarmDMC extends AbstractDMContext { + /** Alarm number, also index into alarm map */ + final int fAlarm; + + public AlarmDMC(AlarmService service, int alarm) { + super(service, new IDMContext[] { service.fAlarmsContext }); + fAlarm = alarm; + } + + @Override + public boolean equals(Object other) { + return baseEquals(other) && ((AlarmDMC)other).fAlarm == fAlarm; + } + + @Override + public int hashCode() { return baseHashCode() + fAlarm; } + @Override + public String toString() { return baseToString() + ".alarm[" + fAlarm + "]"; } //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Data object containing information about the alarm. This object + * references internal service data, so it has to guard agains this data + * being obsolete. + */ + public class AlarmData implements IDMData { + private int fAlarmNumber; + + AlarmData(int alarmNumber) { fAlarmNumber = alarmNumber; } + public boolean isValid() { return fAlarms.containsKey(fAlarmNumber); } + public int getAlarmNumber() { return fAlarmNumber; } + + public int getTriggeringValue() { + if (!isValid()) return -1; + return fAlarms.get(fAlarmNumber); + } + } + + /** + * Context representing the "triggered" status of an alarm with respect to + * a specific timer. Having this object separate from the alarm itself + * allows the alarm object to exist independently of the timers. + */ + public class AlarmStatusContext extends AbstractDMContext { + /** + * An alarm status requires both a timer and alarm context, both of which + * become parents of the status context. + */ + public AlarmStatusContext(AbstractDsfService service, TimerDMC timerCtx, AlarmDMC alarmCtx) { + super(service.getSession().getId(), new IDMContext[] { timerCtx, alarmCtx }); + } + + @Override + public boolean equals(Object other) { return baseEquals(other); } + @Override + public int hashCode() { return baseHashCode(); } + @Override + public String toString() { + return baseToString() + ":alarm_status"; //$NON-NLS-1$ + } + } + + /** + * Data about alarm status. No surprises here. + * + */ + public class AlarmStatusData implements IDMData { + private boolean fIsTriggered; + + public boolean isValid() { return true; } + AlarmStatusData(boolean triggered) { fIsTriggered = triggered; } + public boolean isTriggered() { return fIsTriggered; } + } + + /** + * Event indicating that an alarm has been triggered by a timer. The + * status context object's parents indicate which alarm and timer are + * involved. + */ + public class AlarmTriggeredEvent extends AbstractDMEvent { + public AlarmTriggeredEvent(AlarmStatusContext context) { + super(context); + } + } + + + /** Parent context for all alarms */ + private final IDMContext fAlarmsContext; + + /** Counter for generating alarm numbers */ + private int fAlarmCounter = 1; + + /** Map holding the alarms */ + private Map fAlarms = new TreeMap(); + + /** Constructor requires only the session for this service */ + AlarmService(DsfSession session) { + super(session); + fAlarmsContext = new AbstractDMContext(this, new IDMContext[0]) { + private final Object fHashObject = new Object(); + + @Override + public boolean equals(Object obj) { return (this == obj); }; + + @Override + public int hashCode() { return fHashObject.hashCode(); } + + @Override + public String toString() { return "#alarms"; } //$NON-NLS-1$ + }; + } + + @Override + protected BundleContext getBundleContext() { + return DsfExamplesPlugin.getBundleContext(); + } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + super.initialize( + new RequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleOK() { + doInitialize(requestMonitor); + }}); + } + + /** + * Initialization routine registers the service, and adds it as a listener + * to service events. + */ + private void doInitialize(RequestMonitor requestMonitor) { + getSession().addServiceEventListener(this, null); + register(new String[]{AlarmService.class.getName()}, new Hashtable()); + requestMonitor.done(); + } + + @Override + public void shutdown(RequestMonitor requestMonitor) { + getSession().removeServiceEventListener(this); + unregister(); + super.shutdown(requestMonitor); + } + + public boolean isValid() { return true; } + + @SuppressWarnings("unchecked") + public void getModelData(IDMContext dmc, DataRequestMonitor rm) { + if (dmc instanceof AlarmDMC) { + getAlarmData((AlarmDMC)dmc, (DataRequestMonitor)rm); + return; + } else if (dmc instanceof AlarmStatusContext) { + getAlarmStatusData((AlarmStatusContext)dmc, (DataRequestMonitor)rm); + return; + } else if (dmc == fAlarmsContext) { + ((DataRequestMonitor)rm).setData(this); + } else { + rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, INVALID_HANDLE, "Unknown DMC type", null)); //$NON-NLS-1$ + } + rm.done(); + } + + /** + * Listener for timer ticks events. If a timer triggers an alarm, this + * service needs to issue an alarm triggered event. + * @param event + */ + @DsfServiceEventHandler + public void eventDispatched(TimerTickEvent event) { + final TimerDMC timerContext = event.getDMContext(); + + getServicesTracker().getService(TimerService.class).getTimerData( + event.getDMContext(), + new DataRequestMonitor(getExecutor(), null) { + @Override + protected void handleCompleted() { + if (!getStatus().isOK()) return; + checkAlarmsForTimer(timerContext, getData().getTimerValue()); + } + @Override public String toString() { return "Got timer data: " + getData(); } //$NON-NLS-1$ + }); + } + + /** + * Checks the existing alarms for whether they are triggered by given timer. + * @param timerContext Context of the timer that is changed. + * @param timerValue Current value of the timer. + */ + private void checkAlarmsForTimer(TimerDMC timerContext, int timerValue) { + for (Map.Entry entry : fAlarms.entrySet()) { + if (timerValue == entry.getValue()) { + getSession().dispatchEvent(new AlarmTriggeredEvent( + new AlarmStatusContext(this, timerContext, new AlarmDMC(this, entry.getKey()))), + getProperties()); + } + } + } + + + /** + * Retrieves the list of alarm contexts. + * + *
Note: this method doesn't need to be asynchronous, because all the + * data is stored locally. But using an asynchronous method makes this a + * more applicable example. + * + * @param rm Return data token. + */ + public void getAlarms(DataRequestMonitor rm) { + AlarmDMC[] alarmContexts = new AlarmDMC[fAlarms.size()]; + int i = 0; + for (int alarm : fAlarms.keySet()) { + alarmContexts[i++] = new AlarmDMC(this, alarm); + } + rm.setData(alarmContexts); + rm.done(); + } + + /** + * Retrieves the data object for given alarm context. + * + *
Note: likewise this method doesn't need to be asynchronous. + */ + public void getAlarmData(AlarmDMC alarmCtx, DataRequestMonitor rm) { + if (!fAlarms.containsKey(alarmCtx.fAlarm)) { + rm.setStatus(new Status( + IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, INVALID_HANDLE, "Alarm context invalid", null)); //$NON-NLS-1$ + rm.done(); + return; + } + rm.setData(new AlarmData(alarmCtx.fAlarm)); + rm.done(); + } + + /** + * Returns the alarm status context object, for given timer and alarms. + * + *
Note: this method is synchronous... for variety. + */ + public AlarmStatusContext getAlarmStatus(AlarmDMC alarmCtx, TimerDMC timerCtx) { + return new AlarmStatusContext(this, timerCtx, alarmCtx); + } + + /** + * Returns the data object for given alarm status object. + */ + public void getAlarmStatusData(AlarmStatusContext alarmStatusCtx, final DataRequestMonitor rm) { + final TimerService.TimerDMC timerCtx = DMContexts.getAncestorOfType( + alarmStatusCtx, TimerService.TimerDMC.class); + final AlarmDMC alarmCtx = DMContexts.getAncestorOfType( + alarmStatusCtx, AlarmDMC.class); + + assert alarmCtx != null && timerCtx != null; + + getServicesTracker().getService(TimerService.class).getTimerData( + timerCtx, + new DataRequestMonitor(getExecutor(), rm) { + @Override + protected void handleOK() { + if (!fAlarms.containsKey(alarmCtx.fAlarm)) { + rm.setStatus(new Status( + IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, INVALID_HANDLE, "Alarm context invalid", null)); //$NON-NLS-1$ + rm.done(); + return; + } + boolean isTriggered = getData().getTimerValue() >= fAlarms.get(alarmCtx.fAlarm); + rm.setData(new AlarmStatusData(isTriggered)); + rm.done(); + } + }); + } + + /** + * Creates a new alarm object with given value. + * @return context of the new alarm. + */ + public AlarmDMC createAlarm(int value) { + int newAlarm = fAlarmCounter++; + fAlarms.put(newAlarm, value); + getSession().dispatchEvent(new AlarmsChangedEvent(), getProperties()); + return new AlarmDMC(this, newAlarm); + } + + /** Removes given alarm from service. */ + public void deleteAlarm(AlarmDMC alarmCtx) { + fAlarms.remove(alarmCtx.fAlarm); + getSession().dispatchEvent(new AlarmsChangedEvent(), getProperties()); + } + + /** + * Changes the value of the given alarm. + * @param dmc Alarm to change + * @param newValue New alarm value. + */ + public void setAlarmValue(AlarmDMC dmc, int newValue) { + if (fAlarms.containsKey(dmc.fAlarm)) { + fAlarms.put(dmc.fAlarm, newValue); + } + getSession().dispatchEvent(new AlarmsChangedEvent(), getProperties()); + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmStatusVMNode.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmStatusVMNode.java new file mode 100644 index 00000000000..7f0213ade34 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmStatusVMNode.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * 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 org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.ui.viewmodel.VMDelta; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMNode; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider; +import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; +import org.eclipse.dd.examples.dsf.timers.AlarmService.AlarmDMC; +import org.eclipse.dd.examples.dsf.timers.AlarmService.AlarmStatusContext; +import org.eclipse.dd.examples.dsf.timers.AlarmService.AlarmStatusData; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerDMC; +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.ILabelUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; + +/** + * View model node that determines whether an "alarm triggered" indicator is + * shown in the tree. This indicator is only shown if a given alarm is + * triggered for a given timer. + * + * @see AlarmStatusContext + */ +@SuppressWarnings("restriction") +class AlarmStatusVMNode extends AbstractDMVMNode + implements IElementLabelProvider +{ + public AlarmStatusVMNode(AbstractDMVMProvider provider, DsfSession session) { + super(provider, session, AlarmStatusContext.class); + } + + @Override + protected void updateElementsInSessionThread(final IChildrenUpdate update) { + if (!checkService(AlarmService.class, null, update)) return; + if (!checkService(TimerService.class, null, update)) return; + + AlarmDMC alarmDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), AlarmDMC.class); + TimerDMC timerDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), TimerDMC.class); + if (alarmDmc == null || timerDmc == null) { + update.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, "Required elements not found in path")); //$NON-NLS-1$ + update.done(); + return; + } + + // Get the alarm status DMC then check the triggered value to make sure it's triggered. + final AlarmStatusContext alarmStatusDmc = getServicesTracker().getService(AlarmService.class). + getAlarmStatus(alarmDmc, timerDmc); + getServicesTracker().getService(AlarmService.class).getAlarmStatusData( + alarmStatusDmc, + new DataRequestMonitor(getSession().getExecutor(), null) { + @Override + public void handleCompleted() { + if (isDisposed()) return; + if (!getStatus().isOK()) { + update.setStatus(getStatus()); + } else { + if (getData().isTriggered()) { + update.setChild(createVMContext(alarmStatusDmc), 0); + } + } + update.done(); + }}); + } + + public void update(ILabelUpdate[] updates) { + for (ILabelUpdate update : updates) { + update.setLabel("ALARM TRIGGERED", 0); //$NON-NLS-1$ + update.setImageDescriptor( + DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor( + DsfExamplesPlugin.IMG_ALARM_TRIGGERED), + 0); + update.done(); + } + } + + + public int getDeltaFlags(Object e) { + // This node generates delta if the timers have changed, or if the + // label has changed. + if (e instanceof AlarmService.AlarmTriggeredEvent) { + return IModelDelta.ADDED | IModelDelta.SELECT | IModelDelta.EXPAND; + } + return IModelDelta.NO_CHANGE; + } + + public void buildDelta(Object e, VMDelta parentDelta, int nodeOffset, RequestMonitor requestMonitor) { + // An element is added when and selected upon a triggered event. + // Parent element is also expanded allow element to be selected. + if (e instanceof AlarmService.AlarmTriggeredEvent) { + parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.EXPAND); + parentDelta.addNode( + createVMContext( ((AlarmService.AlarmTriggeredEvent)e).getDMContext() ), + 0, + IModelDelta.ADDED | IModelDelta.SELECT); + } + requestMonitor.done(); + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmsVMNode.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmsVMNode.java new file mode 100644 index 00000000000..885934bf736 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/AlarmsVMNode.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * 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.text.MessageFormat; +import java.util.concurrent.RejectedExecutionException; + +import org.eclipse.dd.dsf.concurrent.ConfinedToDsfExecutor; +import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; +import org.eclipse.dd.dsf.concurrent.DsfRunnable; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.service.IDsfService; +import org.eclipse.dd.dsf.ui.viewmodel.VMDelta; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMNode; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider; +import org.eclipse.dd.dsf.ui.viewmodel.properties.IElementPropertiesProvider; +import org.eclipse.dd.dsf.ui.viewmodel.properties.IPropertiesUpdate; +import org.eclipse.dd.dsf.ui.viewmodel.properties.LabelAttribute; +import org.eclipse.dd.dsf.ui.viewmodel.properties.LabelColumnInfo; +import org.eclipse.dd.dsf.ui.viewmodel.properties.LabelImage; +import org.eclipse.dd.dsf.ui.viewmodel.properties.LabelText; +import org.eclipse.dd.dsf.ui.viewmodel.properties.PropertyBasedLabelProvider; +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.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider; +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.IPresentationContext; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ICellModifier; +import org.eclipse.jface.viewers.TextCellEditor; +import org.eclipse.swt.widgets.Composite; + + +/** + * View model node that defines how alarm DMContexts are displayed in the view. Alarm + * nodes are fairly static, once they are created their label doesn't change. + * @see AlarmDMC + */ +@SuppressWarnings("restriction") +class AlarmsVMNode extends AbstractDMVMNode + implements IElementEditor, IElementPropertiesProvider, IElementLabelProvider +{ + public static final String PROP_ALARM_NUMBER = "alarmNumber"; //$NON-NLS-1$ + public static final String PROP_ALARM_TRIGGER_VALUE = "alarmTriggerValue"; //$NON-NLS-1$ + + private AlarmCellModifier fAlarmCellModifier; + private PropertyBasedLabelProvider fLabelProvider; + + + public AlarmsVMNode(AbstractDMVMProvider provider, DsfSession session) { + super(provider, session, AlarmDMC.class); + + fLabelProvider = new PropertyBasedLabelProvider(); + + LabelColumnInfo idCol = new LabelColumnInfo( + new LabelAttribute[] { + new LabelText(new MessageFormat("Alarm #{0}"), new String[] { PROP_ALARM_NUMBER }), //$NON-NLS-1$ + new LabelImage(DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor(DsfExamplesPlugin.IMG_ALARM)) + }); + fLabelProvider.setColumnInfo(TimersViewColumnPresentation.COL_ID, idCol); + + LabelText valueText = new LabelText(new MessageFormat("{0}"), new String[] { PROP_ALARM_TRIGGER_VALUE }); //$NON-NLS-1$ + LabelColumnInfo valueCol = new LabelColumnInfo( + new LabelAttribute[] { + new LabelText(new MessageFormat("{0}"), new String[] { PROP_ALARM_TRIGGER_VALUE }) //$NON-NLS-1$ + }); + fLabelProvider.setColumnInfo(TimersViewColumnPresentation.COL_VALUE, valueCol); + + } + + @Override + protected void updateElementsInSessionThread(final IChildrenUpdate update) { + if (!checkService(AlarmService.class, null, update)) return; + + // Retrieve the alarm DMContexts, create the corresponding VMCs array, and + // set them as result. + getServicesTracker().getService(AlarmService.class).getAlarms( + new DataRequestMonitor(getSession().getExecutor(), null) { + @Override + public void handleCompleted() { + if (!getStatus().isOK()) { + update.setStatus(getStatus()); + } else { + fillUpdateWithVMCs(update, getData()); + } + update.done(); + }}); + } + + public void update(ILabelUpdate[] updates) { + fLabelProvider.update(updates); + } + + public void update(final IPropertiesUpdate[] updates) { + try { + getSession().getExecutor().execute(new DsfRunnable() { + public void run() { + for (IPropertiesUpdate update : updates) { + updatePropertiesInSessionThread(update); + } + }}); + } catch (RejectedExecutionException e) { + for (IViewerUpdate update : updates) { + handleFailedUpdate(update); + } + } + } + + @ConfinedToDsfExecutor("getSession#getExecutor") + protected void updatePropertiesInSessionThread(final IPropertiesUpdate update) { + final AlarmDMC dmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), AlarmDMC.class); + if (!checkDmc(dmc, update) || !checkService(AlarmService.class, null, update)) return; + + getDMVMProvider().getModelData( + this, update, + getServicesTracker().getService(AlarmService.class, null), + dmc, + new DataRequestMonitor(getSession().getExecutor(), null) { + @Override + protected void handleCompleted() { + /* + * Check that the request was evaluated and data is still + * valid. The request could fail if the state of the + * service changed during the request, but the view model + * has not been updated yet. + */ + if (!getStatus().isOK() || !getData().isValid()) { + assert getStatus().isOK() || + getStatus().getCode() != IDsfService.INTERNAL_ERROR || + getStatus().getCode() != IDsfService.NOT_SUPPORTED; + handleFailedUpdate(update); + return; + } + + update.setProperty(PROP_ALARM_NUMBER, getData().getAlarmNumber()); + update.setProperty(PROP_ALARM_TRIGGER_VALUE, getData().getTriggeringValue()); + update.done(); + } + }, + getExecutor()); + } + + public CellEditor getCellEditor(IPresentationContext context, String columnId, Object element, Composite parent) { + if (TimersViewColumnPresentation.COL_VALUE.equals(columnId)) { + return new TextCellEditor(parent); + } + return null; + } + + // Note: this method is synchronized because IElementEditor.getCellModifier can be called + // on any thread, even though in practice it should be only called on the UI thread. + public synchronized ICellModifier getCellModifier(IPresentationContext context, Object element) { + if (fAlarmCellModifier == null) { + fAlarmCellModifier = new AlarmCellModifier(getSession()); + } + return fAlarmCellModifier; + } + + public int getDeltaFlags(Object e) { + // Since the label for alarms doesn't change, this node will generate + // delta info only if the list of alarms is changed. + if (e instanceof AlarmService.AlarmsChangedEvent) { + return IModelDelta.CONTENT; + } + return IModelDelta.NO_CHANGE; + } + + + public void buildDelta(Object event, VMDelta parentDelta, int nodeOffset, RequestMonitor requestMonitor) { + if (event instanceof AlarmService.AlarmsChangedEvent) { + // The list of alarms has changed, which means that the parent + // node needs to refresh its contents, which in turn will re-fetch the + // elements from this node. + parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT); + } + requestMonitor.done(); + } + + @Override + public synchronized void dispose() { + synchronized(this) { + if (fAlarmCellModifier != null) { + fAlarmCellModifier.dispose(); + } + } + super.dispose(); + } + + public String getPropertyDescription(String property) { + // TODO Auto-generated method stub + return null; + } + + public String getPropertyName(String property) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/ServicesShutdownSequence.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/ServicesShutdownSequence.java new file mode 100644 index 00000000000..8ad1baa9a9c --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/ServicesShutdownSequence.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.timers; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.concurrent.Sequence; +import org.eclipse.dd.dsf.service.DsfServicesTracker; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.service.IDsfService; +import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; + +/** + * Shutdown sequence that stops the services in the timers session. + * + */ +class ServicesShutdownSequence extends Sequence { + + DsfSession fSession; + DsfServicesTracker fTracker; + + ServicesShutdownSequence(DsfSession session) { + super(session.getExecutor()); + fSession = session; + } + + Step[] fSteps = new Step[] { + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + fTracker = new DsfServicesTracker(DsfExamplesPlugin.getBundleContext(), fSession.getId()); + requestMonitor.done(); + } + + @Override + public void rollBack(RequestMonitor requestMonitor) { + fTracker.dispose(); + fTracker = null; + requestMonitor.done(); + } + }, + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(AlarmService.class, requestMonitor); + } + }, + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(TimerService.class, requestMonitor); + } + }, + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + fTracker.dispose(); + fTracker = null; + requestMonitor.done(); + } + } + }; + + @Override + public Step[] getSteps() { return fSteps; } + + /** + * Convenience method that shuts down given service. Only service class + * is used to identify the service. + */ + private void shutdownService(Class clazz, RequestMonitor requestMonitor) { + IDsfService service = fTracker.getService(clazz); + if (service != null) { + service.shutdown(requestMonitor); + } + else { + requestMonitor.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, + "Service '" + clazz.getName() + "' not found.", null)); //$NON-NLS-1$ //$NON-NLS-2$ + requestMonitor.done(); + } + } + +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/ServicesStartupSequence.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/ServicesStartupSequence.java new file mode 100644 index 00000000000..5ac0f2c7252 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/ServicesStartupSequence.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * 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 org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.concurrent.Sequence; +import org.eclipse.dd.dsf.service.DsfSession; + +/** + * Startup sequence for the timers session. With only two services, this is + * a very simple sequence. Last step creates the first timer and alarm. + */ +class ServicesStartupSequence extends Sequence { + + DsfSession fSession; + private TimerService fTimerService = null; + private AlarmService fAlarmService = null; + + + ServicesStartupSequence(DsfSession session) { + super(session.getExecutor()); + fSession = session; + } + + Step[] fSteps = new Step[] { + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + fTimerService = new TimerService(fSession); + fTimerService.initialize(requestMonitor); + }}, + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + fAlarmService = new AlarmService(fSession); + fAlarmService.initialize(requestMonitor); + }}, + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + fTimerService.startTimer(); + fAlarmService.createAlarm(5); + requestMonitor.done(); + }} + }; + + @Override + public Step[] getSteps() { return fSteps; } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimerService.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimerService.java new file mode 100644 index 00000000000..154e4ce1a8e --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimerService.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * 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.Hashtable; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.Future; +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.RequestMonitor; +import org.eclipse.dd.dsf.datamodel.AbstractDMContext; +import org.eclipse.dd.dsf.datamodel.AbstractDMEvent; +import org.eclipse.dd.dsf.datamodel.IDMContext; +import org.eclipse.dd.dsf.datamodel.IDMData; +import org.eclipse.dd.dsf.datamodel.IDMService; +import org.eclipse.dd.dsf.service.AbstractDsfService; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; +import org.osgi.framework.BundleContext; + +/** + * Timer service tracks a set of timers, which are created per user request. + * The timers and their data are provided by the service using the DSF data + * model interfaces. + *

+ * When each timer is created, an event is issued that the service contents are + * changed, and clients should re-query the list of timers. The timers + * increment their value at rate of one per second (but they are not synchronous), + * and an event is issued for every tick. + */ +public class TimerService extends AbstractDsfService + implements IDMService +{ + /** + * Event indicating that the list of timers is changed and the clients + * which display timers should re-query this list. + */ + public class TimersChangedEvent extends AbstractDMEvent { + TimersChangedEvent() { super(fTimersContext); } + } + + /** + * Timer context represents a timer in this service. Clients can use this + * context to retrieve timer data. This class implements the Comaparable + * interfaces so that the objects can be stored in a TreeMap, which keeps them sorted. + */ + public static class TimerDMC extends AbstractDMContext + implements Comparable + { + /** + * Timer number, which is also index to timers map. + */ + final int fTimer; + + public TimerDMC(TimerService service, int timer) { + super(service, new IDMContext[] { service.fTimersContext }); + fTimer = timer; + } + + /** + * Timer context objects are created as needed and not cached, so the + * equals method implementation is critical. + */ + @Override + public boolean equals(Object other) { + return baseEquals(other) && ((TimerDMC)other).fTimer == fTimer; + } + + @Override + public int hashCode() { return baseHashCode() + fTimer; } + + @Override + public String toString() { + return baseToString() + ".timer[" + fTimer + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + public int compareTo(TimerDMC other) { + TimerDMC otherTimer = other; + return (fTimer < otherTimer.fTimer ? -1 : (fTimer == otherTimer.fTimer ? 0 : 1)); + } + } + + /** + * Data about the timer in the service. This object references internal + * service data, so it has to guard agains this data being obsolete. + */ + public class TimerData implements IDMData { + TimerDMC fTimerDMC; + + TimerData(TimerDMC timer) { fTimerDMC = timer; } + public boolean isValid() { return fTimers.containsKey(fTimerDMC); } + public int getTimerNumber() { return fTimerDMC.fTimer; } + + public int getTimerValue() { + if (!isValid()) return -1; + return fTimers.get(fTimerDMC); + } + + @Override public String toString() { return "Timer " + fTimerDMC.fTimer + " = " + getTimerValue(); } //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Event indicating that a timer's value has incremented. The context in + * the event points to the timer that has changed. + */ + public class TimerTickEvent extends AbstractDMEvent { + public TimerTickEvent(TimerDMC context) { + super(context); + } + } + + /** Parnet context for all timers */ + private final IDMContext fTimersContext; + + /** Counter for generating timer numbers */ + private int fTimerCounter = 1; + + /** Map holding the timers */ + private Map fTimers = new TreeMap(); + + private Map> fTimerFutures = new TreeMap>(); + + /** Constructor requires only the session for this service */ + TimerService(DsfSession session) { + super(session); + fTimersContext = new AbstractDMContext(this, new IDMContext[0]) { + private final Object fHashObject = new Object(); + + @Override + public boolean equals(Object obj) { return (this == obj); }; + + @Override + public int hashCode() { return fHashObject.hashCode(); } + + @Override + public String toString() { return "#timers"; } //$NON-NLS-1$ + }; + } + + @Override + protected BundleContext getBundleContext() { + return DsfExamplesPlugin.getBundleContext(); + } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + super.initialize( + new RequestMonitor(getExecutor(), requestMonitor) { + @Override + public void handleOK() { + doInitialize(requestMonitor); + }}); + } + + @Override + public void shutdown(RequestMonitor requestMonitor) { + /* + * Go through all the timer futures and cancel them, so that they + * don't fire any more events. + */ + for (Future future : fTimerFutures.values()) { + future.cancel(false); + } + unregister(); + super.shutdown(requestMonitor); + } + + /** + * Performs the relevant initialization for this service: registering and + * scheduling the timer. + * @param requestMonitor + */ + private void doInitialize(RequestMonitor requestMonitor) { + register(new String[]{TimerService.class.getName()}, new Hashtable()); + requestMonitor.done(); + } + + public boolean isValid() { return true; } + + @SuppressWarnings("unchecked") + public void getModelData(IDMContext dmc, DataRequestMonitor rm) { + if (dmc instanceof TimerDMC) { + getTimerData((TimerDMC)dmc, (DataRequestMonitor)rm); + return; + } else if (dmc == fTimersContext) { + ((DataRequestMonitor)rm).setData(this); + } else { + rm.setStatus(new Status(IStatus.ERROR, DsfExamplesPlugin.PLUGIN_ID, INVALID_HANDLE, "Unknown DMC type", null)); //$NON-NLS-1$ + } + rm.done(); + } + + /** + * Retrieves the list of timer contexts. + * + *
Note: this method doesn't need to be asynchronous, because all the + * data is stored locally. But using an asynchronous method makes this a + * more applicable example. + * + * @param rm Return data token. + */ + public void getTimers(DataRequestMonitor rm) { + rm.setData( fTimers.keySet().toArray(new TimerDMC[fTimers.size()]) ); + rm.done(); + } + + /** + * Retrieves the data object for given timer context. + * + *
Note: likewise this method doesn't need to be asynchronous. + */ + public void getTimerData(TimerDMC context, DataRequestMonitor rm) { + rm.setData(new TimerData(context)); + rm.done(); + } + + /** + * Creates a new timer and returns its context. + */ + public TimerDMC startTimer() { + final TimerDMC newTimer = new TimerDMC(this, fTimerCounter++); + fTimers.put(newTimer, 0); + Future timerFuture = getExecutor().scheduleAtFixedRate( + new Runnable() { + public void run() { + fTimers.put(newTimer, fTimers.get(newTimer) + 1); + getSession().dispatchEvent(new TimerTickEvent(newTimer), getProperties()); + } + @Override + public String toString() { return "Scheduled timer runnable for timer " + newTimer; } //$NON-NLS-1$ + }, + 1, 1, TimeUnit.SECONDS); + fTimerFutures.put(newTimer, timerFuture); + getSession().dispatchEvent(new TimersChangedEvent(), getProperties()); + return newTimer; + } + + /** + * Removes given timer from list of timers. + */ + public void killTimer(TimerDMC timerContext) { + if (fTimers.containsKey(timerContext)) { + fTimers.remove(timerContext); + fTimerFutures.remove(timerContext).cancel(false); + } + getSession().dispatchEvent(new TimersChangedEvent(), getProperties()); + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersModelAdapter.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersModelAdapter.java new file mode 100644 index 00000000000..bd694680da7 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersModelAdapter.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.timers; + +import org.eclipse.dd.dsf.concurrent.ThreadSafe; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.ui.viewmodel.IVMProvider; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMAdapter; +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 TimersModelAdapter extends AbstractDMVMAdapter +{ + TimersVMProvider fViewModelProvider; + + @Override + protected IVMProvider createViewModelProvider(IPresentationContext context) { + /* + * In this example there is only one viewer, so there is only one + * VMProvider. + */ + return fViewModelProvider; + } + + public TimersModelAdapter(DsfSession session, IPresentationContext presentationContext) { + super(session); + fViewModelProvider = new TimersVMProvider(this, presentationContext, getSession()); + } + + TimersVMProvider getTimersVMProvider() { + return fViewModelProvider; + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersVMNode.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersVMNode.java new file mode 100644 index 00000000000..52fd1a7f444 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersVMNode.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * 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.RejectedExecutionException; + +import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; +import org.eclipse.dd.dsf.concurrent.DsfRunnable; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.service.IDsfService; +import org.eclipse.dd.dsf.ui.viewmodel.VMDelta; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMNode; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider; +import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerDMC; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerData; +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.ILabelUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; + + +/** + * View model node that defines how timer DMContexts are displayed in the view. Timers + * change with every tick of the timer, so the label has to be repained + * upon timer tick events. + * @see TimerDMC + */ +@SuppressWarnings("restriction") +class TimersVMNode extends AbstractDMVMNode + implements IElementLabelProvider +{ + + public TimersVMNode(AbstractDMVMProvider provider, DsfSession session) { + super(provider, session, TimerDMC.class); + } + + @Override + protected void updateElementsInSessionThread(final IChildrenUpdate update) { + if (!checkService(AlarmService.class, null, update)) return; + + // Retrieve the timer DMContexts, create the corresponding VMCs array, and + // set them as result. + getServicesTracker().getService(TimerService.class).getTimers( + new DataRequestMonitor(getSession().getExecutor(), null) { + @Override + public void handleCompleted() { + if (!getStatus().isOK()) { + update.setStatus(getStatus()); + } else { + fillUpdateWithVMCs(update, getData()); + } + update.done(); + }}); + } + + + public void update(final ILabelUpdate[] updates) { + try { + getSession().getExecutor().execute(new DsfRunnable() { + public void run() { + updateLabelInSessionThread(updates); + }}); + } catch (RejectedExecutionException e) { + for (ILabelUpdate update : updates) { + handleFailedUpdate(update); + } + } + } + + + protected void updateLabelInSessionThread(ILabelUpdate[] updates) { + for (final ILabelUpdate update : updates) { + final TimerDMC dmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), TimerDMC.class); + if (!checkDmc(dmc, update) || !checkService(TimerService.class, null, update)) continue; + + getDMVMProvider().getModelData( + this, update, + getServicesTracker().getService(TimerService.class, null), + dmc, + new DataRequestMonitor(getSession().getExecutor(), null) { + @Override + protected void handleCompleted() { + /* + * Check that the request was evaluated and data is still + * valid. The request could fail if the state of the + * service changed during the request, but the view model + * has not been updated yet. + */ + if (!getStatus().isOK() || !getData().isValid()) { + assert getStatus().isOK() || + getStatus().getCode() != IDsfService.INTERNAL_ERROR || + getStatus().getCode() != IDsfService.NOT_SUPPORTED; + handleFailedUpdate(update); + return; + } + + /* + * If columns are configured, call the protected methods to + * fill in column values. + */ + String[] localColumns = update.getPresentationContext().getColumns(); + if (localColumns == null) localColumns = new String[] { null }; + + for (int i = 0; i < localColumns.length; i++) { + fillColumnLabel(dmc, getData(), localColumns[i], i, update); + } + update.done(); + } + }, + getExecutor()); + } + } + + protected void fillColumnLabel(TimerDMC dmContext, TimerData dmData, String columnId, int idx, + ILabelUpdate update) + { + if (TimersViewColumnPresentation.COL_ID.equals(columnId)) { + update.setLabel( Integer.toString(dmData.getTimerNumber()), idx ); + update.setImageDescriptor( + DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor(DsfExamplesPlugin.IMG_TIMER), idx); + } else if (TimersViewColumnPresentation.COL_VALUE.equals(columnId)) { + update.setLabel( Integer.toString(dmData.getTimerValue()), idx); + } + } + + public int getDeltaFlags(Object e) { + // This node generates delta if the timers have changed, or if the + // label has changed. + if (e instanceof TimerService.TimerTickEvent) { + return IModelDelta.STATE; + } else if (e instanceof TimerService.TimersChangedEvent) { + return IModelDelta.CONTENT; + } + return IModelDelta.NO_CHANGE; + } + + public void buildDelta(Object e, VMDelta parentDelta, int nodeOffset, RequestMonitor requestMonitor) { + if (e instanceof TimerService.TimerTickEvent) { + // Add delta indicating that the VMC for the given timer context + // has changed. + parentDelta.addNode( createVMContext(((TimerService.TimerTickEvent)e).getDMContext()), IModelDelta.STATE ); + } else if (e instanceof TimerService.TimersChangedEvent) { + // The list of timers has changed, which means that the parent + // node needs to refresh its contents, which in turn will re-fetch the + // elements from this node. + parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT); + } + requestMonitor.done(); + } +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersVMProvider.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersVMProvider.java new file mode 100644 index 00000000000..92f55d00aec --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersVMProvider.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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 org.eclipse.core.runtime.IAdaptable; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.ui.viewmodel.AbstractVMAdapter; +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.dd.dsf.ui.viewmodel.DefaultVMModelProxyStrategy; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; + +/** + * + */ +@SuppressWarnings("restriction") +public class TimersVMProvider extends AbstractDMVMProvider { + + /** + * 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 "Timers View Root"; //$NON-NLS-1$ + } + }; + + private DefaultVMModelProxyStrategy fModelProxyStrategy; + + + /** Enumeration of possible layouts for the timers view model */ + public enum ViewLayout { ALARMS_AT_TOP, TIMERS_AT_TOP } + + public TimersVMProvider(AbstractVMAdapter adapter, IPresentationContext presentationContext, DsfSession session) { + super(adapter, presentationContext, session); + setViewLayout(ViewLayout.ALARMS_AT_TOP); + } + + + public Object getViewerInputObject() { + return fViewerInputObject; + } + + /** + * Configures a new layout for the timers view model. + * @param layout New layout to use. + */ + public void setViewLayout(ViewLayout layout) { + if (layout == ViewLayout.ALARMS_AT_TOP) { + IRootVMNode root = new RootVMNode(this); + IVMNode alarmsNode = new AlarmsVMNode(this, getSession()); + IVMNode timersNode0 = new TimersVMNode(this, getSession()); + addChildNodes(root, new IVMNode[] { alarmsNode, timersNode0 }); + IVMNode timersNode = new TimersVMNode(this, getSession()); + addChildNodes(alarmsNode, new IVMNode[] { timersNode }); + IVMNode alarmStatusNode = new AlarmStatusVMNode(this, getSession()); + addChildNodes(timersNode, new IVMNode[] { alarmStatusNode }); + setRootNode(root); + } else if (layout == ViewLayout.TIMERS_AT_TOP) { + IRootVMNode root = new RootVMNode(this); + IVMNode timersNode = new TimersVMNode(this, getSession()); + addChildNodes(root, new IVMNode[] { timersNode }); + IVMNode alarmsNode = new AlarmsVMNode(this, getSession()); + addChildNodes(timersNode, new IVMNode[] { alarmsNode }); + IVMNode alarmStatusNode = new AlarmStatusVMNode(this, getSession()); + addChildNodes(alarmsNode, new IVMNode[] { alarmStatusNode }); + setRootNode(root); + } + + /* TODO: replace with an event + fModelProxyStrategy.fireModelChanged( + new ModelDelta(getRootElement(), IModelDelta.CONTENT)); + */ + } + + @Override + public IColumnPresentation createColumnPresentation(IPresentationContext context, Object element) { + return new TimersViewColumnPresentation(); + } + + @Override + public String getColumnPresentationId(IPresentationContext context, Object element) { + return TimersViewColumnPresentation.ID; + } + +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersView.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersView.java new file mode 100644 index 00000000000..f9b4afe2be7 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersView.java @@ -0,0 +1,296 @@ +/******************************************************************************* + * 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.dd.dsf.concurrent.DefaultDsfExecutor; +import org.eclipse.dd.dsf.concurrent.DsfExecutor; +import org.eclipse.dd.dsf.datamodel.IDMContext; +import org.eclipse.dd.dsf.service.DsfServicesTracker; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.ui.viewmodel.datamodel.IDMVMContext; +import org.eclipse.dd.examples.dsf.DsfExamplesPlugin; +import org.eclipse.dd.examples.dsf.timers.TimerService.TimerDMC; +import org.eclipse.dd.examples.dsf.timers.TimersVMProvider.ViewLayout; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentationFactory; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementContentProvider; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxyFactory; +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.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.part.ViewPart; + + +/** + * Example view which displays data from timers and alarms DSF services. This + * starts a new DSF session and configures the services for it. Then it + * configures a data model provider to process the service data and display it + * in a flexible-hierarchy asynchronous viewer. + */ +@SuppressWarnings("restriction") +public class TimersView extends ViewPart { + + /** Asynchronous tree viewer from the platform debug.ui plugin. */ + private TreeModelViewer fViewer; + + /** DSF executor to use for a new session with timers and alarms services */ + private DsfExecutor fExecutor; + + /** DSF session */ + private DsfSession fSession; + + /** DSF services tracker used by actions in the viewer. */ + private DsfServicesTracker fServices; + + /** Adapter used to provide view model for flexible-hierarchy viewer */ + private TimersModelAdapter fTimersModelAdapter; + + /** Action which toggles the layout in the viewer */ + private Action fToggleLayoutAction; + + /** Action that adds a new timer */ + private Action fAddTimerAction; + + /** Action that adds a new alarm */ + private Action fAddAlarmAction; + + /** Action that removes the selected alarm or timer */ + private Action fRemoveAction; + + public TimersView() {} + + /** + * This is a callback that will allow us to create the viewer and + * initialize it. For this view, it creates the DSF session, along + * with its services. Then it creates the viewer model adapter and + * registers it with the session. + */ + @Override + public void createPartControl(Composite parent) { + /* + * Create the Flexible Hierarchy viewer. Also create a presentation + * context which will be given to the content/label provider adapters + * to distinguish this view from other flex-hierarchy views. + */ + final IPresentationContext presentationContext = new PresentationContext("org.eclipse.dd.examples.dsf.timers"); //$NON-NLS-1$ + fViewer = new TreeModelViewer(parent, SWT.VIRTUAL | SWT.FULL_SELECTION, presentationContext); + + /* + * Create the executor, which will be used exclusively with this view, + * as well as a session and a services tracker for managing references + * to services. + */ + fExecutor = new DefaultDsfExecutor(); + fSession = DsfSession.startSession(fExecutor, "org.eclipse.dd.examples.dsf.timers"); //$NON-NLS-1$ + fServices = new DsfServicesTracker(DsfExamplesPlugin.getBundleContext(), fSession.getId()); + + /* + * Start the services using a sequence. The sequence runs in the + * dispatch thread, so we have to block this thread using Future.get() + * until it completes. The Future.get() will throw an exception if + * the sequence fails. + */ + ServicesStartupSequence startupSeq = new ServicesStartupSequence(fSession); + fSession.getExecutor().execute(startupSeq); + try { + startupSeq.get(); + } catch (InterruptedException e) { assert false; + } catch (ExecutionException e) { assert false; + } + + /* + * Create the flexible hierarchy content/label adapter. Then register + * it with the session. + */ + fTimersModelAdapter = new TimersModelAdapter(fSession, presentationContext); + fSession.registerModelAdapter(IElementContentProvider.class, fTimersModelAdapter); + fSession.registerModelAdapter(IModelProxyFactory.class, fTimersModelAdapter); + fSession.registerModelAdapter(IColumnPresentationFactory.class, fTimersModelAdapter); + + /* + * Set the root element for the timers tree viewer. The root element + * comes from the content provider. + */ + fViewer.setInput(fTimersModelAdapter.getTimersVMProvider().getViewerInputObject()); + + makeActions(); + contributeToActionBars(); + } + + @Override + public void dispose() { + try { + /* + * First dispose the view model, which is the client of services. + * We are not in the dispatch thread + */ + fSession.getExecutor().submit(new Runnable() { + public void run() { + fSession.unregisterModelAdapter(IElementContentProvider.class); + fSession.unregisterModelAdapter(IModelProxyFactory.class); + fSession.unregisterModelAdapter(IColumnPresentationFactory.class); + fTimersModelAdapter.dispose(); + fTimersModelAdapter = null; + }}).get(); + + // Then invoke the shutdown sequence for the services. + ServicesShutdownSequence shutdownSeq = new ServicesShutdownSequence(fSession); + fSession.getExecutor().execute(shutdownSeq); + try { + shutdownSeq.get(); + } catch (InterruptedException e) { assert false; + } catch (ExecutionException e) { assert false; + } + + // Finally end the session and the executor: + fSession.getExecutor().submit(new Runnable() { + public void run() { + DsfSession.endSession(fSession); + fSession = null; + fExecutor.shutdown(); + fExecutor = null; + }}).get(); + } catch (InterruptedException e) { + } catch (ExecutionException e) { + } + //fViewer.dispose(); + super.dispose(); + } + + private void contributeToActionBars() { + IActionBars bars = getViewSite().getActionBars(); + fillLocalToolBar(bars.getToolBarManager()); + } + + private void fillLocalToolBar(IToolBarManager manager) { + manager.add(fToggleLayoutAction); + manager.add(fAddTimerAction); + manager.add(fAddAlarmAction); + manager.add(fRemoveAction); + manager.add(new Separator()); + } + + private void makeActions() { + fToggleLayoutAction = new Action("Toggle Layout", IAction.AS_CHECK_BOX) { //$NON-NLS-1$ + @Override + public void run() { + // Get the toggle state of the action while on UI thread. + final ViewLayout layout = isChecked() ? ViewLayout.ALARMS_AT_TOP : ViewLayout.TIMERS_AT_TOP; + + // Switch to executor thread to perform the change in layout. + fExecutor.submit(new Runnable() { public void run() { + fTimersModelAdapter.getTimersVMProvider().setViewLayout(layout); + }}); + } + }; + fToggleLayoutAction.setToolTipText("Toggle Layout"); //$NON-NLS-1$ + fToggleLayoutAction.setImageDescriptor(DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor( + DsfExamplesPlugin.IMG_LAYOUT_TOGGLE)); + + fAddTimerAction = new Action("Add New Timer") { //$NON-NLS-1$ + @Override + public void run() { + fExecutor.submit(new Runnable() { public void run() { + // Only need to create the new timer, the events will cause + // the view to refresh. + fServices.getService(TimerService.class).startTimer(); + }}); + } + }; + fAddTimerAction.setToolTipText("Add new timer"); //$NON-NLS-1$ + fAddTimerAction.setImageDescriptor(DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor( + DsfExamplesPlugin.IMG_TIMER)); + + fAddAlarmAction = new Action("Add New Alarm") { //$NON-NLS-1$ + @Override + public void run() { + // Ask user for the new alarm value. + InputDialog inputDialog = new InputDialog( + fViewer.getControl().getShell(), + "New Alarm", //$NON-NLS-1$ + "Please enter alarm time", //$NON-NLS-1$ + "", //$NON-NLS-1$ + new IInputValidator() { + public String isValid(String input) { + try { + int i= Integer.parseInt(input); + if (i <= 0) + return "Please enter a positive integer"; //$NON-NLS-1$ + + } catch (NumberFormatException x) { + return "Please enter a positive integer"; //$NON-NLS-1$ + } + return null; + } + } + ); + if (inputDialog.open() != Window.OK) return; + int tmpAlarmValue = -1; + try { + tmpAlarmValue = Integer.parseInt(inputDialog.getValue()); + } catch (NumberFormatException x) { assert false; } + final int alarmValue = tmpAlarmValue; + fExecutor.submit(new Runnable() { public void run() { + // Create the new alarm. + fServices.getService(AlarmService.class).createAlarm(alarmValue); + }}); + } + }; + fAddAlarmAction.setToolTipText("Add new alarm"); //$NON-NLS-1$ + fAddAlarmAction.setImageDescriptor(DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor( + DsfExamplesPlugin.IMG_ALARM)); + + fRemoveAction = new Action("Remove") { //$NON-NLS-1$ + @Override + public void run() { + final Object selectedElement = ((IStructuredSelection)fViewer.getSelection()).getFirstElement(); + if (!(selectedElement instanceof IDMVMContext)) return; + final IDMContext selectedDmc = ((IDMVMContext)selectedElement).getDMContext(); + // Based on the DMC from the selection, call the appropriate service to + // remove the item. + if (selectedDmc instanceof TimerDMC) { + fExecutor.submit(new Runnable() { public void run() { + fServices.getService(TimerService.class).killTimer( + ((TimerDMC)selectedDmc)); + }}); + } else if (selectedDmc instanceof AlarmService.AlarmDMC) { + fExecutor.submit(new Runnable() { public void run() { + fServices.getService(AlarmService.class).deleteAlarm( + (AlarmService.AlarmDMC)selectedDmc); + }}); + } + } + }; + fRemoveAction.setToolTipText("Remove selected item"); //$NON-NLS-1$ + fRemoveAction.setImageDescriptor(DsfExamplesPlugin.getDefault().getImageRegistry().getDescriptor( + DsfExamplesPlugin.IMG_REMOVE)); + } + + /** + * Passing the focus request to the viewer's control. + */ + @Override + public void setFocus() { + fViewer.getControl().setFocus(); + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersViewColumnPresentation.java b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersViewColumnPresentation.java new file mode 100644 index 00000000000..ba24f6b68dc --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/TimersViewColumnPresentation.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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 org.eclipse.dd.examples.dsf.DsfExamplesPlugin; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IColumnPresentation; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; +import org.eclipse.jface.resource.ImageDescriptor; + +/** + * + */ +@SuppressWarnings("restriction") +public class TimersViewColumnPresentation implements IColumnPresentation { + + public static final String ID = DsfExamplesPlugin.PLUGIN_ID + ".TIMER_COLUMN_PRESENTATION_ID"; //$NON-NLS-1$ + public static final String COL_ID = ID + ".COL_ID"; //$NON-NLS-1$ + public static final String COL_VALUE = ID + ".COL_VALUE"; //$NON-NLS-1$ + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#init(org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext) + public void init(IPresentationContext context) {} + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#dispose() + public void dispose() {} + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#getAvailableColumns() + public String[] getAvailableColumns() { + return new String[] { COL_ID, COL_VALUE }; + } + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#getHeader(java.lang.String) + public String getHeader(String id) { + if (COL_ID.equals(id)) { + return "ID"; //$NON-NLS-1$ + } else if (COL_VALUE.equals(id)) { + return "Value"; //$NON-NLS-1$ + } + return null; + } + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#getId() + public String getId() { + return ID; + } + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#getImageDescriptor(java.lang.String) + public ImageDescriptor getImageDescriptor(String id) { + return null; + } + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#getInitialColumns() + public String[] getInitialColumns() { + return getAvailableColumns(); + } + + // @see org.eclipse.debug.internal.ui.viewers.provisional.IColumnPresentation#isOptional() + public boolean isOptional() { + return true; + } + +} diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/doc-files/package-1.png b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/doc-files/package-1.png new file mode 100644 index 00000000000..0e7c8683d61 Binary files /dev/null and b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/doc-files/package-1.png differ diff --git a/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/package.html b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/package.html new file mode 100644 index 00000000000..a4b276be052 --- /dev/null +++ b/plugins/org.eclipse.dd.examples.dsf/src/org/eclipse/dd/examples/dsf/timers/package.html @@ -0,0 +1,28 @@ + + + + + Eclipse Device Debug - Debugger Services Framework - Data Model + + +Example demostrationg use of Debugger Services Framework (DSF) data +model interfaces.
+

Package Specification

+The example consists of two services, one that monitors a set of timers +(TimerService), and another that tracks a set of alarms +(AlarmService).  The timers are incremented at a rate of one per +second, although for visual interest, timers are not synchronous as +each timer is incremented at a slightly different moment.  There +is also a "Timers" view that displays the timers and alarms, and has +actions for adding and removing new timers and alarms.  Most +interestingly, the view has a toggle action, which switches the layout +of the view from having timers at the root level, with alarms listed +below each timer, to having alarms at the root level with timers listed +below each alarm.
+
+
+
+ + diff --git a/plugins/org.eclipse.dd.gdb/.classpath b/plugins/org.eclipse.dd.gdb/.classpath new file mode 100644 index 00000000000..304e86186aa --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/org.eclipse.dd.gdb/.project b/plugins/org.eclipse.dd.gdb/.project new file mode 100644 index 00000000000..5d4f83fccd0 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/.project @@ -0,0 +1,28 @@ + + + org.eclipse.dd.gdb + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/plugins/org.eclipse.dd.gdb/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.dd.gdb/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..c9eb6de4787 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,65 @@ +#Thu Jun 07 11:08:01 PDT 2007 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +org.eclipse.jdt.core.compiler.problem.nullReference=ignore +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/plugins/org.eclipse.dd.gdb/META-INF/MANIFEST.MF b/plugins/org.eclipse.dd.gdb/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..ec667323401 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/META-INF/MANIFEST.MF @@ -0,0 +1,22 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: GDB DSF Debugger Integration Core +Bundle-Vendor: Eclipse.org +Bundle-SymbolicName: org.eclipse.dd.gdb;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.dd.gdb.internal.GdbPlugin +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.dd.dsf, + org.eclipse.dd.dsf.debug, + org.eclipse.dd.mi, + org.eclipse.debug.core, + org.eclipse.cdt.core, + org.eclipse.cdt.launch, + org.eclipse.cdt.debug.core, + org.eclipse.cdt.debug.mi.core +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Export-Package: org.eclipse.dd.gdb.breakpoints, + org.eclipse.dd.gdb.launching, + org.eclipse.dd.gdb.service, + org.eclipse.dd.gdb.service.command diff --git a/plugins/org.eclipse.dd.gdb/about.html b/plugins/org.eclipse.dd.gdb/about.html new file mode 100644 index 00000000000..cb740ae8bc8 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/about.html @@ -0,0 +1,24 @@ + + + + +About +

About This Content

+ +

June 5, 2007

+

License

+ +

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.gdb/build.properties b/plugins/org.eclipse.dd.gdb/build.properties new file mode 100644 index 00000000000..786b1df9364 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + about.html diff --git a/plugins/org.eclipse.dd.gdb/plugin.xml b/plugins/org.eclipse.dd.gdb/plugin.xml new file mode 100644 index 00000000000..b57c16f117e --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/plugin.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/breakpoints/CBreakpointGdbThreadsFilterExtension.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/breakpoints/CBreakpointGdbThreadsFilterExtension.java new file mode 100644 index 00000000000..1a81d3f5fc3 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/breakpoints/CBreakpointGdbThreadsFilterExtension.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2007 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.gdb.breakpoints; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.cdt.debug.core.model.ICBreakpoint; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.dd.dsf.datamodel.DMContexts; +import org.eclipse.dd.dsf.debug.service.IDsfBreakpointExtension; +import org.eclipse.dd.dsf.debug.service.IRunControl.IContainerDMContext; +import org.eclipse.dd.dsf.debug.service.IRunControl.IExecutionDMContext; + +/** + * + */ +public class CBreakpointGdbThreadsFilterExtension implements IDsfBreakpointExtension { + + private Map> fFilteredThreadsByTarget = + new HashMap>(1); + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpointExtension#initialize(org.eclipse.cdt.debug.core.model.ICBreakpoint) + */ + public void initialize(ICBreakpoint breakpoint) { + // TODO: Initialize fFilteredThreadsByTarget with current IContainerDMContext[] + // TODO: IRunControl? + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpoint#getTargetFilters() + */ + public IContainerDMContext[] getTargetFilters() throws CoreException { + Set set = fFilteredThreadsByTarget.keySet(); + return set.toArray( new IContainerDMContext[set.size()] ); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpoint#getThreadFilters(org.eclipse.cdt.debug.core.model.ICDebugTarget) + */ + public IExecutionDMContext[] getThreadFilters( IContainerDMContext target ) throws CoreException { + Set set = fFilteredThreadsByTarget.get( target ); + return ( set != null ) ? set.toArray( new IExecutionDMContext[set.size()] ) : null; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpoint#removeTargetFilter(org.eclipse.cdt.debug.core.model.ICDebugTarget) + */ + public void removeTargetFilter( IContainerDMContext target ) throws CoreException { + if ( fFilteredThreadsByTarget.containsKey( target ) ) { + fFilteredThreadsByTarget.remove( target ); + } + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpoint#removeThreadFilters(org.eclipse.cdt.debug.core.model.ICThread[]) + */ + public void removeThreadFilters( IExecutionDMContext[] threads ) throws CoreException { + if ( threads != null && threads.length > 0 ) { + IContainerDMContext target = DMContexts.getAncestorOfType(threads[0], IContainerDMContext.class); + if ( fFilteredThreadsByTarget.containsKey( target ) ) { + Set set = fFilteredThreadsByTarget.get( target ); + if ( set != null ) { + set.removeAll( Arrays.asList( threads ) ); + if ( set.isEmpty() ) { + fFilteredThreadsByTarget.remove( target ); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpoint#setTargetFilter(org.eclipse.cdt.debug.core.model.ICDebugTarget) + */ + public void setTargetFilter( IContainerDMContext target ) throws CoreException { + fFilteredThreadsByTarget.put( target, null ); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.debug.core.model.ICBreakpoint#setThreadFilters(org.eclipse.cdt.debug.core.model.ICThread[]) + */ + public void setThreadFilters( IExecutionDMContext[] threads ) throws CoreException { + if ( threads != null && threads.length > 0 ) { + IContainerDMContext target = DMContexts.getAncestorOfType(threads[0], IContainerDMContext.class); + fFilteredThreadsByTarget.put( target, new HashSet( Arrays.asList( threads ) ) ); + } + } + +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/GdbPlugin.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/GdbPlugin.java new file mode 100644 index 00000000000..679534ccf74 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/internal/GdbPlugin.java @@ -0,0 +1,110 @@ +package org.eclipse.dd.gdb.internal; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.RejectedExecutionException; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; +import org.eclipse.dd.dsf.concurrent.Query; +import org.eclipse.dd.gdb.launching.GdbLaunch; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class GdbPlugin extends Plugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.dd.gdb"; //$NON-NLS-1$ + + // The shared instance + private static GdbPlugin plugin; + + private static BundleContext fgBundleContext; + + /** + * The constructor + */ + public GdbPlugin() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext) + */ + @Override + public void start(BundleContext context) throws Exception { + fgBundleContext = context; + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext) + */ + @Override + public void stop(BundleContext context) throws Exception { + shutdownActiveLaunches(); + plugin = null; + super.stop(context); + fgBundleContext = null; + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static GdbPlugin getDefault() { + return plugin; + } + + public static BundleContext getBundleContext() { + return fgBundleContext; + } + + /** + * Shuts down any active launches. We must shutdown any active sessions + * and services associated with this plugin before this plugin is stopped. + * Any attempts to use the plugins {@link BundleContext} after the plugin + * is shut down will result in exceptions. + */ + private void shutdownActiveLaunches() { + for (ILaunch launch : DebugPlugin.getDefault().getLaunchManager().getLaunches()) { + if (launch instanceof GdbLaunch && ((GdbLaunch)launch).getSession().isActive()) { + final GdbLaunch gdbLaunch = (GdbLaunch)launch; + + Query launchShutdownQuery = new Query() { + @Override + protected void execute(DataRequestMonitor rm) { + gdbLaunch.shutdownSession(rm); + } + }; + + try { + gdbLaunch.getSession().getExecutor().execute(launchShutdownQuery); + } catch (RejectedExecutionException e) { + // We can get this exception if the session is shutdown concurrently + // to this method running. + break; + } + + // The Query.get() method is a synchronous call which blocks until the + // query completes. + try { + launchShutdownQuery.get(); + } catch (InterruptedException e) { + getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, "InterruptedException while shutting down PDA debugger launch " + gdbLaunch, e.getCause())); //$NON-NLS-1$ + } catch (ExecutionException e) { + getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, "Exception while shutting down PDA debugger launch " + gdbLaunch, e.getCause())); //$NON-NLS-1$ + } + } + } + } + +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/GdbLaunch.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/GdbLaunch.java new file mode 100644 index 00000000000..95592eafde4 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/GdbLaunch.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * 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.gdb.launching; + +import java.util.concurrent.ExecutionException; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.ConfinedToDsfExecutor; +import org.eclipse.dd.dsf.concurrent.DefaultDsfExecutor; +import org.eclipse.dd.dsf.concurrent.DsfExecutor; +import org.eclipse.dd.dsf.concurrent.DsfRunnable; +import org.eclipse.dd.dsf.concurrent.ImmediateExecutor; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.concurrent.Sequence; +import org.eclipse.dd.dsf.concurrent.ThreadSafe; +import org.eclipse.dd.dsf.service.DsfServiceEventHandler; +import org.eclipse.dd.dsf.service.DsfServicesTracker; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.service.IDsfService; +import org.eclipse.dd.gdb.internal.GdbPlugin; +import org.eclipse.dd.gdb.service.command.GDBControl; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.Launch; +import org.eclipse.debug.core.model.ISourceLocator; +import org.eclipse.debug.core.model.ITerminate; + +/** + * The only object in the model that implements the traditional interfaces. + */ +@ThreadSafe +public class GdbLaunch extends Launch + implements ITerminate +{ + private DefaultDsfExecutor fExecutor; + private DsfSession fSession; + private DsfServicesTracker fTracker; + private boolean fInitialized = false; + private boolean fShutDown = false; + + + public GdbLaunch(ILaunchConfiguration launchConfiguration, String mode, ISourceLocator locator) { + super(launchConfiguration, mode, locator); + + // Create the dispatch queue to be used by debugger control and services + // that belong to this launch + final DefaultDsfExecutor dsfExecutor = new DefaultDsfExecutor(GdbLaunchDelegate.GDB_DEBUG_MODEL_ID); + dsfExecutor.prestartCoreThread(); + fExecutor = dsfExecutor; + fSession = DsfSession.startSession(fExecutor, GdbLaunchDelegate.GDB_DEBUG_MODEL_ID); + } + + public DsfExecutor getDsfExecutor() { return fExecutor; } + + @ConfinedToDsfExecutor("getExecutor") + public void initializeControl() + throws CoreException + { + + Runnable initRunnable = new DsfRunnable() { + public void run() { + fTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fSession.getId()); + fSession.addServiceEventListener(GdbLaunch.this, null); + + fInitialized = true; + fireChanged(); + } + }; + + // Invoke the execution code and block waiting for the result. + try { + fExecutor.submit(initRunnable).get(); + } catch (InterruptedException e) { + new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, "Error initializing launch", e); //$NON-NLS-1$ + } catch (ExecutionException e) { + new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, "Error initializing launch", e); //$NON-NLS-1$ + } + } + + public DsfSession getSession() { return fSession; } + + /////////////////////////////////////////////////////////////////////////// + // IServiceEventListener + @DsfServiceEventHandler public void eventDispatched(GDBControl.ExitedEvent event) { + shutdownSession(new RequestMonitor(ImmediateExecutor.getInstance(), null)); + } + + /////////////////////////////////////////////////////////////////////////// + // ITerminate + @Override + public boolean canTerminate() { + return super.canTerminate() && fInitialized && !fShutDown; + } + + @Override + public boolean isTerminated() { + return super.isTerminated() || fShutDown; + } + + + @Override + public void terminate() throws DebugException { + if (fShutDown) return; + super.terminate(); + } + // ITerminate + /////////////////////////////////////////////////////////////////////////// + + /** + * Shuts down the services, the session and the executor associated with + * this launch. + *

+ * Note: The argument request monitor to this method should NOT use the + * executor that belongs to this launch. By the time the shutdown is + * complete, this executor will not be dispatching anymore and the + * request monitor will never be invoked. Instead callers should use + * the {@link ImmediateExecutor}. + *

+ * @param rm The request monitor invoked when the shutdown is complete. + */ + @ConfinedToDsfExecutor("getSession().getExecutor()") + public void shutdownSession(final RequestMonitor rm) { + if (fShutDown) { + rm.done(); + return; + } + fShutDown = true; + + Sequence shutdownSeq = new ShutdownSequence( + getDsfExecutor(), fSession.getId(), + new RequestMonitor(fSession.getExecutor(), rm) { + @Override + public void handleCompleted() { + fSession.removeServiceEventListener(GdbLaunch.this); + if (!getStatus().isOK()) { + GdbPlugin.getDefault().getLog().log(new MultiStatus( + GdbPlugin.PLUGIN_ID, -1, new IStatus[]{getStatus()}, "Session shutdown failed", null)); //$NON-NLS-1$ + } + // Last order of business, shutdown the dispatch queue. + fTracker.dispose(); + fTracker = null; + DsfSession.endSession(fSession); + // endSession takes a full dispatch to distribute the + // session-ended event, finish step only after the dispatch. + fExecutor.shutdown(); + fExecutor = null; + fireTerminate(); + + rm.setStatus(getStatus()); + rm.done(); + } + }); + fExecutor.execute(shutdownSeq); + } + + @SuppressWarnings("unchecked") + @Override + public Object getAdapter(Class adapter) { + // Must force adapters to be loaded. + Platform.getAdapterManager().loadAdapter(this, adapter.getName()); + return super.getAdapter(adapter); + } +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/GdbLaunchDelegate.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/GdbLaunchDelegate.java new file mode 100644 index 00000000000..0535874e5cf --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/GdbLaunchDelegate.java @@ -0,0 +1,290 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 QNX Software 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: + * QNX Software Systems - Initial API and implementation + *******************************************************************************/ +package org.eclipse.dd.gdb.launching; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.cdt.core.model.ICProject; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.launch.AbstractCLaunchDelegate; +import org.eclipse.cdt.launch.internal.ui.LaunchMessages; +import org.eclipse.cdt.launch.internal.ui.LaunchUIPlugin; +import org.eclipse.cdt.utils.pty.PTY; +import org.eclipse.cdt.utils.spawner.ProcessFactory; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.ThreadSafe; +import org.eclipse.dd.dsf.debug.model.DsfMemoryBlockRetrieval; +import org.eclipse.dd.dsf.debug.service.IMemory.IMemoryDMContext; +import org.eclipse.dd.dsf.service.DsfServicesTracker; +import org.eclipse.dd.gdb.internal.GdbPlugin; +import org.eclipse.dd.gdb.service.command.GDBControl; +import org.eclipse.dd.mi.service.command.AbstractCLIProcess; +import org.eclipse.dd.mi.service.command.MIInferiorProcess; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.IStatusHandler; +import org.eclipse.debug.core.model.ILaunchConfigurationDelegate2; +import org.eclipse.debug.core.model.IMemoryBlockRetrieval; +import org.eclipse.debug.core.model.IPersistableSourceLocator; +import org.eclipse.debug.core.model.ISourceLocator; +import org.eclipse.debug.core.sourcelookup.IPersistableSourceLocator2; + +/** + * The launch configuration delegate for the CDI debugger session types. + */ +@ThreadSafe +public class GdbLaunchDelegate extends AbstractCLaunchDelegate + implements ILaunchConfigurationDelegate2 +{ + public final static String GDB_DEBUG_MODEL_ID = "org.eclipse.dd.dsf.gdb"; //$NON-NLS-1$ + + /* (non-Javadoc) + * @see org.eclipse.cdt.launch.AbstractCLaunchDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public void launch( ILaunchConfiguration config, String mode, ILaunch launch, IProgressMonitor monitor ) throws CoreException { + if ( monitor == null ) { + monitor = new NullProgressMonitor(); + } + if ( mode.equals( ILaunchManager.DEBUG_MODE ) ) { + launchDebugger( config, launch, monitor ); + } + } + + private void launchDebugger( ILaunchConfiguration config, ILaunch launch, IProgressMonitor monitor ) throws CoreException { + monitor.beginTask("Launching debugger session", 10); //$NON-NLS-1$ + if ( monitor.isCanceled() ) { + return; + } + try { + String debugMode = config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN ); + if ( debugMode.equals( ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN ) ) { + launchLocalDebugSession( config, launch, monitor ); + } + } + finally { + monitor.done(); + } + } + + private void launchLocalDebugSession( ILaunchConfiguration config, ILaunch l, IProgressMonitor monitor ) throws CoreException { + if ( monitor.isCanceled() ) { + return; + } + final GdbLaunch launch = (GdbLaunch)l; + + monitor.subTask( "Debugging local C/C++ application" ); //$NON-NLS-1$ + IPath exePath = verifyProgramPath( config ); + ICProject project = verifyCProject( config ); + if ( exePath != null ) { + verifyBinary( project, exePath ); + } + + setDefaultSourceLocator(launch, config); + + monitor.worked( 1 ); + + + // Create and invoke the launch sequence to create the debug control and services + final LaunchSequence launchSequence = + new LaunchSequence(launch.getSession(), launch, exePath); + launch.getSession().getExecutor().execute(launchSequence); + try { + launchSequence.get(); + } catch (InterruptedException e1) { + throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Interrupted Exception in dispatch thread", e1)); //$NON-NLS-1$ + } catch (ExecutionException e1) { + throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Error in launch sequence", e1.getCause())); //$NON-NLS-1$ + } + + launch.initializeControl(); + + // Add the CLI and "inferior" process objects to the launch. + final AtomicReference cliProcessRef = new AtomicReference(); + final AtomicReference inferiorProcessRef = new AtomicReference(); + try { + launch.getDsfExecutor().submit( new Callable() { + public Object call() throws CoreException { + DsfServicesTracker tracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), launch.getSession().getId()); + GDBControl gdb = tracker.getService(GDBControl.class); + if (gdb != null) { + cliProcessRef.set(gdb.getCLIProcess()); + inferiorProcessRef.set(gdb.getInferiorProcess()); + } + tracker.dispose(); + return null; + } + }).get(); + launch.addProcess(DebugPlugin.newProcess(launch, cliProcessRef.get(), "gdb")); //$NON-NLS-1$ + launch.addProcess(DebugPlugin.newProcess(launch, inferiorProcessRef.get(), exePath.lastSegment())); + } catch (InterruptedException e) { + throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, 0, "Interrupted while waiting for get process callable.", e)); //$NON-NLS-1$ + } catch (ExecutionException e) { + throw (CoreException)e.getCause(); + } catch (RejectedExecutionException e) { + throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, 0, "Debugger shut down before launch was completed.", e)); //$NON-NLS-1$ + } + + // Create a memory retrieval and register it with session + try { + launch.getDsfExecutor().submit( new Callable() { + public Object call() throws CoreException { + DsfServicesTracker tracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), launch.getSession().getId()); + GDBControl gdbControl = tracker.getService(GDBControl.class); + if (gdbControl != null) { + IMemoryBlockRetrieval memRetrieval = new DsfMemoryBlockRetrieval( + GDB_DEBUG_MODEL_ID, (IMemoryDMContext)gdbControl.getControlDMContext()); + launch.getSession().registerModelAdapter(IMemoryBlockRetrieval.class, memRetrieval); + } + tracker.dispose(); + return null; + } + }).get(); + } catch (InterruptedException e) { + throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, 0, "Interrupted while waiting for get process callable.", e)); //$NON-NLS-1$ + } catch (ExecutionException e) { + throw (CoreException)e.getCause(); + } catch (RejectedExecutionException e) { + throw new CoreException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, 0, "Debugger shut down before launch was completed.", e)); //$NON-NLS-1$ + } + } + + + /* (non-Javadoc) + * @see org.eclipse.cdt.launch.AbstractCLaunchDelegate#getPluginID() + */ + @Override + protected String getPluginID() { + return LaunchUIPlugin.getUniqueIdentifier(); + } + + /** + * Performs a runtime exec on the given command line in the context of the + * specified working directory, and returns the resulting process. If the + * current runtime does not support the specification of a working + * directory, the status handler for error code + * ERR_WORKING_DIRECTORY_NOT_SUPPORTED is queried to see if + * the exec should be re-executed without specifying a working directory. + * + * @param cmdLine + * the command line + * @param workingDirectory + * the working directory, or null + * @return the resulting process or null if the exec is + * cancelled + * @see Runtime + */ + protected Process exec( String[] cmdLine, String[] environ, File workingDirectory, boolean usePty ) throws CoreException { + Process p = null; + try { + if ( workingDirectory == null ) { + p = ProcessFactory.getFactory().exec( cmdLine, environ ); + } + else { + if ( usePty && PTY.isSupported() ) { + p = ProcessFactory.getFactory().exec( cmdLine, environ, workingDirectory, new PTY() ); + } + else { + p = ProcessFactory.getFactory().exec( cmdLine, environ, workingDirectory ); + } + } + } + catch( IOException e ) { + if ( p != null ) { + p.destroy(); + } + abort( "Error starting process.", e, ICDTLaunchConfigurationConstants.ERR_INTERNAL_ERROR ); //$NON-NLS-1$ + } + catch( NoSuchMethodError e ) { + // attempting launches on 1.2.* - no ability to set working + // directory + IStatus status = new Status( IStatus.ERROR, LaunchUIPlugin.getUniqueIdentifier(), ICDTLaunchConfigurationConstants.ERR_WORKING_DIRECTORY_NOT_SUPPORTED, LaunchMessages.getString( "LocalDsfLaunchDelegate.9" ), e ); //$NON-NLS-1$ + IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler( status ); + if ( handler != null ) { + Object result = handler.handleStatus( status, this ); + if ( result instanceof Boolean && ((Boolean)result).booleanValue() ) { + p = exec( cmdLine, environ, null, usePty ); + } + } + } + return p; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.launch.AbstractCLaunchDelegate#preLaunchCheck(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public boolean preLaunchCheck( ILaunchConfiguration config, String mode, IProgressMonitor monitor ) throws CoreException { + // no pre launch check for core file + if ( mode.equals( ILaunchManager.DEBUG_MODE ) ) { + if ( ICDTLaunchConfigurationConstants.DEBUGGER_MODE_CORE.equals( config.getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE, ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN ) ) ) + return true; + } + return super.preLaunchCheck( config, mode, monitor ); + } + + /////////////////////////////////////////////////////////////////////////// + // ILaunchConfigurationDelegate2 + @Override + public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException { + return false; + } + + @Override + public boolean finalLaunchCheck(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) throws CoreException { + return true; + } + + @Override + public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws CoreException { + // Need to configure the source locator before creating the launch + // because once the launch is created and added to launch manager, + // the adapters will be created for the whole session, including + // the source lookup adapter. + ISourceLocator locator = getSourceLocator(configuration); + + return new GdbLaunch(configuration, mode, locator); + } + + private ISourceLocator getSourceLocator(ILaunchConfiguration configuration) throws CoreException { + String type = configuration.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, (String)null); + if (type == null) { + type = configuration.getType().getSourceLocatorId(); + } + if (type != null) { + IPersistableSourceLocator locator = DebugPlugin.getDefault().getLaunchManager().newSourceLocator(type); + String memento = configuration.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, (String)null); + if (memento == null) { + locator.initializeDefaults(configuration); + } else { + if(locator instanceof IPersistableSourceLocator2) + ((IPersistableSourceLocator2)locator).initializeFromMemento(memento, configuration); + else + locator.initializeFromMemento(memento); + } + return locator; + } + return null; + } +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/LaunchSequence.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/LaunchSequence.java new file mode 100644 index 00000000000..9f49b87e9a0 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/LaunchSequence.java @@ -0,0 +1,267 @@ +/******************************************************************************* + * 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.gdb.launching; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.cdt.debug.core.CDebugCorePlugin; +import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; +import org.eclipse.cdt.debug.internal.core.sourcelookup.CSourceLookupDirector; +import org.eclipse.cdt.debug.mi.core.IMILaunchConfigurationConstants; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.DataRequestMonitor; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.concurrent.Sequence; +import org.eclipse.dd.dsf.debug.service.StepQueueManager; +import org.eclipse.dd.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; +import org.eclipse.dd.dsf.debug.service.IRunControl.IContainerDMContext; +import org.eclipse.dd.dsf.service.DsfServiceEventHandler; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.gdb.internal.GdbPlugin; +import org.eclipse.dd.gdb.service.GDBRunControl; +import org.eclipse.dd.gdb.service.command.GDBControl; +import org.eclipse.dd.mi.service.CSourceLookup; +import org.eclipse.dd.mi.service.ExpressionService; +import org.eclipse.dd.mi.service.MIBreakpoints; +import org.eclipse.dd.mi.service.MIBreakpointsManager; +import org.eclipse.dd.mi.service.MIMemory; +import org.eclipse.dd.mi.service.MIModules; +import org.eclipse.dd.mi.service.MIRegisters; +import org.eclipse.dd.mi.service.MIStack; +import org.eclipse.dd.mi.service.command.commands.MIBreakInsert; +import org.eclipse.dd.mi.service.command.commands.MIExecRun; +import org.eclipse.dd.mi.service.command.events.MIStoppedEvent; +import org.eclipse.dd.mi.service.command.output.MIBreakInsertInfo; +import org.eclipse.dd.mi.service.command.output.MIInfo; +import org.eclipse.debug.core.DebugException; + +public class LaunchSequence extends Sequence { + + public class EntryPointHitEventListener { + boolean fAborted = false; + boolean fFinished = false; + final RequestMonitor fRequestMonitor; + + EntryPointHitEventListener(RequestMonitor requestMonitor) { + fRequestMonitor = requestMonitor; + } + + @DsfServiceEventHandler + public void eventDispatched(MIStoppedEvent e) { + fFinished = true; + if (!fAborted) { + fSession.removeServiceEventListener(this); + fRequestMonitor.done(); + } + } + } + + + Step[] fSteps = new Step[] { + // Create and initialize the Connection service. + new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + // + // Create the connection. + // + fCommandControl = new GDBControl( + fSession, getGDBPath(), fExecPath, GDBControl.SessionType.RUN, 30); + fCommandControl.initialize(requestMonitor); + } + }, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new GDBRunControl(fSession).initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new StepQueueManager(fSession).initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new MIMemory(fSession).initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new MIModules(fSession).initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new MIStack(fSession).initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new ExpressionService(fSession).initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + fSourceLookup = new CSourceLookup(fSession); + fSourceLookup.initialize(requestMonitor); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + fSourceLookup.setSourceLookupDirector( + fCommandControl.getGDBDMContext(), + ((CSourceLookupDirector)fLaunch.getSourceLocator())); + requestMonitor.done(); + }}, + new Step() { @Override + public void execute(final RequestMonitor requestMonitor) { + // Create the low-level breakpoint service + final MIBreakpoints bpService = new MIBreakpoints(fSession); + bpService.initialize(new RequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleOK() { + requestMonitor.done(); + } + }); + }}, + new Step() { @Override + public void execute(final RequestMonitor requestMonitor) { + // Create high-level breakpoint service and install breakpoints + // for the GDB debug context. + final MIBreakpointsManager bpmService = new MIBreakpointsManager(fSession, CDebugCorePlugin.PLUGIN_ID); + bpmService.initialize(new RequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleOK() { + bpmService.startTrackingBreakpoints(fCommandControl.getGDBDMContext(), requestMonitor); + } + }); + }}, + new Step() { @Override + public void execute(RequestMonitor requestMonitor) { + new MIRegisters(fSession).initialize(requestMonitor); + }}, + /* + * If needed, insert breakpoint at main and run to it. + */ + new Step() { + private boolean fStopInMain = false; + private String fStopSymbol = null; + + /** + * @return The return value actually indicates whether the get operation succeeded, + * not whether to stop. + */ + private boolean readStopAtMain(RequestMonitor requestMonitor) { + try { + fStopInMain = fLaunch.getLaunchConfiguration().getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, false ); + } catch (CoreException e) { + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, "Cannot retrieve the entry point symbol", e)); //$NON-NLS-1$ + requestMonitor.done(); + return false; + } + return true; + } + + private boolean readStopSymbol(RequestMonitor requestMonitor) { + try { + fStopSymbol = fLaunch.getLaunchConfiguration().getAttribute( ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN_SYMBOL, ICDTLaunchConfigurationConstants.DEBUGGER_STOP_AT_MAIN_SYMBOL_DEFAULT ); + } catch (CoreException e) { + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.CONFIGURATION_INVALID, "Cannot retrieve the entry point symbol", e)); //$NON-NLS-1$ + requestMonitor.done(); + return false; + } + return true; + } + + @Override + public void execute(final RequestMonitor requestMonitor) { + if (!readStopAtMain(requestMonitor)) return; + if (!fStopInMain) { + requestMonitor.done(); + return; + } + + if (!readStopSymbol(requestMonitor)) return; + + // Create a listener to wait for the stopped event, and register as even handler. + // This handler will execute the requestMonitor. + final EntryPointHitEventListener entryPointHitListener = new EntryPointHitEventListener(requestMonitor); + fSession.addServiceEventListener(entryPointHitListener, null); + + // Create a time-out, to abort if breakpoint not hit. + fSession.getExecutor().schedule( + new Runnable() { public void run() { + // Only process the event if we have not finished yet (hit the breakpoint). + if (!entryPointHitListener.fFinished) { + // Mark the listener as aborted, and unregister it as event listener. + entryPointHitListener.fAborted = true; + fSession.removeServiceEventListener(entryPointHitListener); + + // Submit the error result for the step. + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, "Timed out running to entry point.", null)); //$NON-NLS-1$ + requestMonitor.done(); + } + }}, + 60, TimeUnit.SECONDS); + + // Insert a breakpoint at the requested stop symbol. + fCommandControl.queueCommand( + new MIBreakInsert( + (IBreakpointsTargetDMContext)fCommandControl.getControlDMContext(), + true, false, null, 0, fStopSymbol, 0), + new DataRequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleOK() { + + // After the break-insert is done, execute the -exec-run command. + fCommandControl.queueCommand( + new MIExecRun((IContainerDMContext)fCommandControl.getControlDMContext(), new String[0]), + new DataRequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleOK() { + // Note : Do we not need to do something with the original requestMonitor? + // Do nothing. Execution was resumed and the EntryPointHitEventListener + // will resume execution + } + } + ); + } + }); + } + }, + }; + + DsfSession fSession; + GdbLaunch fLaunch; + IPath fExecPath; + + GDBControl fCommandControl; + CSourceLookup fSourceLookup; + + public LaunchSequence(DsfSession session, GdbLaunch launch, IPath execPath) { + super(session.getExecutor()); + fSession = session; + fLaunch = launch; + fExecPath = execPath; + } + + @Override + public Step[] getSteps() { + return fSteps; + } + + private IPath getGDBPath() { + IPath retVal = new Path("gdb.exe"); //$NON-NLS-1$ + try { + retVal = new Path( fLaunch.getLaunchConfiguration().getAttribute( IMILaunchConfigurationConstants.ATTR_DEBUG_NAME, IMILaunchConfigurationConstants.DEBUGGER_DEBUG_NAME_DEFAULT ) ); + } catch (CoreException e) { + } + return retVal; + } + +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/ShutdownSequence.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/ShutdownSequence.java new file mode 100644 index 00000000000..b902a74bd80 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/launching/ShutdownSequence.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * 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.gdb.launching; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.dd.dsf.concurrent.DsfExecutor; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.concurrent.Sequence; +import org.eclipse.dd.dsf.service.DsfServicesTracker; +import org.eclipse.dd.dsf.service.IDsfService; +import org.eclipse.dd.gdb.internal.GdbPlugin; +import org.eclipse.dd.gdb.service.GDBRunControl; +import org.eclipse.dd.gdb.service.command.GDBControl; +import org.eclipse.dd.mi.service.CSourceLookup; +import org.eclipse.dd.mi.service.ExpressionService; +import org.eclipse.dd.mi.service.MIBreakpoints; +import org.eclipse.dd.mi.service.MIBreakpointsManager; +import org.eclipse.dd.mi.service.MIMemory; +import org.eclipse.dd.mi.service.MIModules; +import org.eclipse.dd.mi.service.MIRegisters; +import org.eclipse.dd.mi.service.MIStack; + +public class ShutdownSequence extends Sequence { + + String fSessionId; + + String fApplicationName; + + String fDebugModelId; + + DsfServicesTracker fTracker; + + public ShutdownSequence(DsfExecutor executor, String sessionId, RequestMonitor requestMonitor) { + super(executor, requestMonitor); + fSessionId = sessionId; + } + + @Override + public Step[] getSteps() { + return fSteps; + } + + private final Step[] fSteps = new Step[] { new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + assert GdbPlugin.getBundleContext() != null; + fTracker = new DsfServicesTracker(GdbPlugin.getBundleContext(), fSessionId); + requestMonitor.done(); + } + + @Override + public void rollBack(RequestMonitor requestMonitor) { + fTracker.dispose(); + fTracker = null; + requestMonitor.done(); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(MIRegisters.class, requestMonitor); + } +// TODO: As Pawel about the necessity of this step +// Not clear on the purpose of this step since the next one does it also +// (stopTrackingBreakpoints() is called as part of the shutdown method) +// Besides, the run control is already gone so removing breakpoints from +// the back-end is bound to fail... +// }, new Step() { +// @Override +// public void execute(final RequestMonitor requestMonitor) { +// MIBreakpointsManager bpm = fTracker.getService(MIBreakpointsManager.class); +// GDBControl commandControl = fTracker.getService(GDBControl.class); +// if (bpm != null && commandControl != null) { +// bpm.stopTrackingBreakpoints( +// commandControl.getGDBDMContext(), +// new RequestMonitor(getExecutor(), requestMonitor) { +// @Override +// protected void handleCompleted() { +// // If un-installing breakpoints fails, log the error but continue shutting down. +// if (!getStatus().isOK()) { +// DsfGdbPlugin.getDefault().getLog().log(getStatus()); +// } +// requestMonitor.done(); +// } +// }); +// } else { +// requestMonitor.setStatus(new Status(IStatus.ERROR, DsfGdbPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, +// "Needed services not found.", null)); //$NON-NLS-1$ +// requestMonitor.done(); +// } +// } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(MIBreakpointsManager.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(MIBreakpoints.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(CSourceLookup.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(ExpressionService.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(MIStack.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(MIModules.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(MIMemory.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(GDBRunControl.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + shutdownService(GDBControl.class, requestMonitor); + } + }, new Step() { + @Override + public void execute(RequestMonitor requestMonitor) { + fTracker.dispose(); + fTracker = null; + requestMonitor.done(); + } + } }; + + @SuppressWarnings("unchecked") + private void shutdownService(Class clazz, final RequestMonitor requestMonitor) { + IDsfService service = fTracker.getService(clazz); + if (service != null) { + service.shutdown(new RequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleCompleted() { + if (!getStatus().isOK()) { + GdbPlugin.getDefault().getLog().log(getStatus()); + } + requestMonitor.done(); + } + }); + } else { + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfService.INTERNAL_ERROR, + "Service '" + clazz.getName() + "' not found.", null)); //$NON-NLS-1$//$NON-NLS-2$ + requestMonitor.done(); + } + } +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/GDBRunControl.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/GDBRunControl.java new file mode 100644 index 00000000000..b6b3aabdd57 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/GDBRunControl.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 + * Ericsson AB - Modified for additional functionality + *******************************************************************************/ + +package org.eclipse.dd.gdb.service; + + +import java.util.Arrays; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; + +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.RequestMonitor; +import org.eclipse.dd.dsf.datamodel.DMContexts; +import org.eclipse.dd.dsf.debug.service.IRunControl; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.gdb.internal.GdbPlugin; +import org.eclipse.dd.gdb.service.command.GDBControl; +import org.eclipse.dd.gdb.service.command.GDBControlDMContext; +import org.eclipse.dd.mi.service.IMIExecutionDMContext; +import org.eclipse.dd.mi.service.MIRunControl; +import org.eclipse.dd.mi.service.command.commands.CLIInfoThreads; +import org.eclipse.dd.mi.service.command.events.MIEvent; +import org.eclipse.dd.mi.service.command.events.MIThreadExitEvent; +import org.eclipse.dd.mi.service.command.output.CLIInfoThreadsInfo; +public class GDBRunControl extends MIRunControl { + + /** + * Implement a custom execution data for threads in order to provide additional + * information. This object can be made separate from IExecutionDMData after + * the deprecated method: IDMService.getModelData() is no longer used. + */ + public static class GDBThreadData { + private final String fId; + private final String fName; + + GDBThreadData(String id, String name) { + fId = id; + fName = name; + } + + public String getName() { + return fName; + } + public String getId() { return fId; } + + public boolean isDebuggerAttached() { return true; } + } + + /** + * Implement a custom execution data the process in order to provide additional + * information. This object can be made separate from IExecutionDMData after + * the deprecated method: IDMService.getModelData() is no longer used. + */ + public static class GDBProcessData { + private final String fName; + + GDBProcessData(String name) { + fName = name; + } + + public String getName() { + return fName; + } + } + + private GDBControl fGdb; + + // Record list of execution contexts + private IExecutionDMContext[] fOldExecutionCtxts; + + + public GDBRunControl(DsfSession session) { + super(session); + } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + super.initialize( + new RequestMonitor(getExecutor(), requestMonitor) { + @Override + public void handleOK() { + doInitialize(requestMonitor); + }}); + } + + private void doInitialize(final RequestMonitor requestMonitor) { + + fGdb = getServicesTracker().getService(GDBControl.class); + register(new String[]{IRunControl.class.getName(), MIRunControl.class.getName()}, new Hashtable()); + + requestMonitor.done(); + } + + @Override + public void shutdown(final RequestMonitor requestMonitor) { + unregister(); + super.shutdown(requestMonitor); + } + + @Override + public void suspend(IExecutionDMContext context, RequestMonitor requestMonitor){ + if (canSuspend(context)) { + fGdb.interrupt(); + } else { + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Context cannot be suspended.", null)); //$NON-NLS-1$ + } + requestMonitor.done(); + } + + + /* + * This is a HACK. Remove this method when GDB starts to account exited threads id in -thread-list-id command. + * Exited threads are reported in -thread-list-id command even after an exit event is raised by GDB + * Hence, this method needs a special handling in case of GDB. + * Raises ExitEvent when a thread really exits from the system. This is done by comparing the execution contexts list + * See bug 200615 for details. + */ + @Override + public void getExecutionContexts(IContainerDMContext c, final DataRequestMonitor rm) { + DataRequestMonitor rm1 = new DataRequestMonitor( + getExecutor(), rm) { + @Override + protected void handleOK() { + raiseExitEvents(getData()); + fOldExecutionCtxts = getData(); + rm.setData(fOldExecutionCtxts); + rm.done(); + } + }; + super.getExecutionContexts(c, rm1); + } + + public void getProcessData(GDBControlDMContext gdbDmc, DataRequestMonitor rm) { + rm.setData( new GDBProcessData(fGdb.getExecutablePath().lastSegment()) ); + rm.done(); + } + + public void getThreadData(final IMIExecutionDMContext execDmc, final DataRequestMonitor rm) { + IContainerDMContext containerDmc = DMContexts.getAncestorOfType(execDmc, IContainerDMContext.class); + assert containerDmc != null; // Every exec context should have a container as an ancestor. + getCache().execute(new CLIInfoThreads(containerDmc), + new DataRequestMonitor(getExecutor(), rm) { + @Override + protected void handleOK() { + rm.setData( createThreadInfo(execDmc, getData()) ); + rm.done(); + } + }); + } + + private GDBThreadData createThreadInfo(IMIExecutionDMContext dmc, CLIInfoThreadsInfo info){ + for (CLIInfoThreadsInfo.ThreadInfo thread : info.getThreadInfo()) { + if(Integer.parseInt(thread.getId()) == dmc.getThreadId()){ + //fMapThreadIds.put(thread.getId(), String.valueOf(dmc.getId())); + return new GDBThreadData(thread.getOsId(), thread.getName()); + } + } + return new GDBThreadData("",""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + + private void raiseExitEvents(IExecutionDMContext[] ctxts){ + if(ctxts == null || fOldExecutionCtxts == null) + return; + List list = Arrays.asList(ctxts); + List oldThreadList = Arrays.asList(fOldExecutionCtxts); + Iterator iterator = oldThreadList.iterator(); + while(iterator.hasNext()){ + IExecutionDMContext ctxt = iterator.next(); + if(! list.contains(ctxt)){ + IContainerDMContext containerDmc = DMContexts.getAncestorOfType(ctxt, IContainerDMContext.class); + MIEvent e = new MIThreadExitEvent(containerDmc, ((IMIExecutionDMContext)ctxt).getThreadId()); + // Dispatch DsfMIThreadExitEvent + getSession().dispatchEvent(e, getProperties()); + } + } + } + + +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBCLIProcess.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBCLIProcess.java new file mode 100644 index 00000000000..6c6c80c251b --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBCLIProcess.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2007 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.gdb.service.command; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.RejectedExecutionException; + +import org.eclipse.dd.dsf.concurrent.DsfRunnable; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.mi.service.command.AbstractCLIProcess; + +/** + * + */ +class GDBCLIProcess extends AbstractCLIProcess { + + public GDBCLIProcess(GDBControl commandControl, boolean useExecConsole) throws IOException { + super(commandControl, useExecConsole); + } + + + /** + * @see java.lang.Process#waitFor() + */ + @Override + public int waitFor() throws InterruptedException { + if (!DsfSession.isSessionActive(getSession().getId())) return 0; + + Process process = null; + try { + process = getSession().getExecutor().submit(new Callable() { + public Process call() throws Exception { + if (isDisposed()) return null; + return ((GDBControl)getCommandControl()).getGDBProcess(); + }}).get(); + } catch (RejectedExecutionException e) { + } catch (ExecutionException e) { + } + if (process == null) return 0; + return process.waitFor(); + } + + + /** + * @see java.lang.Process#exitValue() + */ + @Override + public int exitValue() { + if (!DsfSession.isSessionActive(getSession().getId())) return 0; + try { + return getSession().getExecutor().submit(new Callable() { + public Integer call() throws Exception { + if (!DsfSession.isSessionActive(getSession().getId())) { + return new Integer(-1); + } else { + if (isDisposed()) return new Integer(-1); + GDBControl gdb = (GDBControl)getCommandControl(); + if (!gdb.isGDBExited()) { + throw new IllegalThreadStateException("GDB Process has not exited"); //$NON-NLS-1$ + } + return gdb.getGDBExitCode(); + } + }}).get().intValue(); + } catch (RejectedExecutionException e) { + } catch (InterruptedException e) { + } catch (ExecutionException e) { + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException)e.getCause(); + } + } + return 0; + } + /** + * @see java.lang.Process#destroy() + */ + @Override + public void destroy() { + try { + getSession().getExecutor().execute(new DsfRunnable() { public void run() { + if (!DsfSession.isSessionActive(getSession().getId())) return; + if (isDisposed()) return; + GDBControl gdb = (GDBControl)getCommandControl(); + gdb.destroy(); + }}); + } catch (RejectedExecutionException e) { + // Session disposed. + } + } + + +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBControl.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBControl.java new file mode 100644 index 00000000000..ea79486a158 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBControl.java @@ -0,0 +1,596 @@ +/******************************************************************************* + * 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 + * Ericsson - Modified for additional features in DSF Reference implementation + *******************************************************************************/ +package org.eclipse.dd.gdb.service.command; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.cdt.utils.spawner.ProcessFactory; +import org.eclipse.cdt.utils.spawner.Spawner; +import org.eclipse.core.runtime.IPath; +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.DsfRunnable; +import org.eclipse.dd.dsf.concurrent.RequestMonitor; +import org.eclipse.dd.dsf.concurrent.Sequence; +import org.eclipse.dd.dsf.datamodel.AbstractDMEvent; +import org.eclipse.dd.dsf.debug.service.command.CommandCache; +import org.eclipse.dd.dsf.debug.service.command.ICommandControl; +import org.eclipse.dd.dsf.service.DsfServiceEventHandler; +import org.eclipse.dd.dsf.service.DsfSession; +import org.eclipse.dd.dsf.service.IDsfService; +import org.eclipse.dd.gdb.internal.GdbPlugin; +import org.eclipse.dd.mi.service.command.AbstractCLIProcess; +import org.eclipse.dd.mi.service.command.AbstractMIControl; +import org.eclipse.dd.mi.service.command.CLIEventProcessor; +import org.eclipse.dd.mi.service.command.MIControlDMContext; +import org.eclipse.dd.mi.service.command.MIInferiorProcess; +import org.eclipse.dd.mi.service.command.MIRunControlEventProcessor; +import org.eclipse.dd.mi.service.command.commands.MIGDBExit; +import org.eclipse.dd.mi.service.command.commands.MIGDBShowExitCode; +import org.eclipse.dd.mi.service.command.commands.MIInterpreterExecConsole; +import org.eclipse.dd.mi.service.command.output.MIGDBShowExitCodeInfo; +import org.eclipse.dd.mi.service.command.output.MIInfo; +import org.eclipse.debug.core.DebugException; +import org.osgi.framework.BundleContext; + +/** + * GDB Debugger control implementation. This implementation extends the + * base MI control implementation to provide the GDB-specific debugger + * features. This includes:
+ * - CLI console support,
+ * - inferior process status tracking.
+ */ +public class GDBControl extends AbstractMIControl { + + /** + * Event indicating that the back end process process has started. + */ + public static class StartedEvent extends AbstractDMEvent { + public StartedEvent(GDBControlDMContext context) { + super(context); + } + } + + + /** + * Event indicating that the back end process has terminated. + */ + public static class ExitedEvent extends AbstractDMEvent { + public ExitedEvent(GDBControlDMContext context) { + super(context); + } + } + + private static int fgInstanceCounter = 0; + private final GDBControlDMContext fControlDmc; + + public enum SessionType { RUN, ATTACH, CORE } + private SessionType fSessionType; + + private boolean fConnected = false; + private boolean fUseInterpreterConsole; + + private MonitorJob fMonitorJob; + private IPath fGdbPath; + private IPath fExecPath; + private Process fProcess; + private int fGDBExitValue; + final private int fGDBLaunchTimeout; + + private CommandCache fCommandCache; + + private MIRunControlEventProcessor fMIEventProcessor; + private CLIEventProcessor fCLICommandProcessor; + private AbstractCLIProcess fCLIProcess; + private MIInferiorProcess fInferiorProcess; + + public GDBControl(DsfSession session, IPath gdbPath, IPath execPath, SessionType type, int gdbLaunchTimeout) { + super(session); + fSessionType = type; + fGdbPath = gdbPath; + fExecPath = execPath; + fGDBLaunchTimeout = gdbLaunchTimeout; + fControlDmc = new GDBControlDMContext(session.getId(), getClass().getName() + ":" + ++fgInstanceCounter); //$NON-NLS-1$ + + } + + @Override + protected BundleContext getBundleContext() { + return GdbPlugin.getBundleContext(); + } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + super.initialize( new RequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleOK() { + doInitialize(requestMonitor); + } + }); + } + + public void doInitialize(final RequestMonitor requestMonitor) { + final Sequence.Step[] initializeSteps = new Sequence.Step[] { + new GDBProcessStep(InitializationShutdownStep.Direction.INITIALIZING), + new MonitorJobStep(InitializationShutdownStep.Direction.INITIALIZING), + new CommandMonitoringStep(InitializationShutdownStep.Direction.INITIALIZING), + new CheckInterpreterConsoleStep(InitializationShutdownStep.Direction.INITIALIZING), + new CommandProcessorsStep(InitializationShutdownStep.Direction.INITIALIZING), + new RegisterStep(InitializationShutdownStep.Direction.INITIALIZING), + }; + + Sequence startupSequence = new Sequence(getExecutor(), requestMonitor) { + @Override public Step[] getSteps() { return initializeSteps; } + }; + getExecutor().execute(startupSequence); + } + + @Override + public void shutdown(final RequestMonitor requestMonitor) { + final Sequence.Step[] shutdownSteps = new Sequence.Step[] { + new RegisterStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), + new CommandProcessorsStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), + new CheckInterpreterConsoleStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), + new CommandMonitoringStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), + new MonitorJobStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), + new GDBProcessStep(InitializationShutdownStep.Direction.SHUTTING_DOWN), + }; + Sequence shutdownSequence = new Sequence(getExecutor(), requestMonitor) { + @Override public Step[] getSteps() { return shutdownSteps; } + }; + getExecutor().execute(shutdownSequence); + + } + + @Override + public MIControlDMContext getControlDMContext() { + return fControlDmc; + } + + /** + * More strongly typed version of {@link #getControlDMContext()}. + */ + public GDBControlDMContext getGDBDMContext() { + return (GDBControlDMContext)getControlDMContext(); + } + + public SessionType getSessionType() { + return fSessionType; + } + + public boolean canInterrupt() { + return fProcess instanceof Spawner; + } + + public void interrupt() { + if (fProcess instanceof Spawner) { + Spawner gdbSpawner = (Spawner) fProcess; + gdbSpawner.interrupt(); + } + } + + public void destroy() { + if (fProcess instanceof Spawner) { + Spawner gdbSpawner = (Spawner) fProcess; + gdbSpawner.destroy(); + } + } + + public void terminate(final RequestMonitor rm) { + // Schedule a runnable to be executed 2 seconds from now. + // If we don't get a response to the quit command, this + // runnable will kill the task. + final Future quitTimeoutFuture = getExecutor().schedule( + new DsfRunnable() { + public void run() { + if (!isGDBExited()) + destroy(); + rm.done(); + } + + @Override + protected boolean isExecutionRequired() { + return false; + } + }, + 2, TimeUnit.SECONDS); + + MIGDBExit cmd = new MIGDBExit(fControlDmc); + queueCommand( + cmd, + new DataRequestMonitor(getExecutor(), rm) { + @Override + public void handleCompleted() { + // Cancel the time out runnable (if it hasn't run yet). + quitTimeoutFuture.cancel(false); + if (!getStatus().isOK() && !isGDBExited()) { + destroy(); + } + rm.done(); + } + } + ); + } + + public boolean isConnected() { + return fInferiorProcess.getState() != MIInferiorProcess.State.TERMINATED && fConnected; + } + + void setConnected(boolean connected) { + fConnected = connected; + } + + public Process getGDBProcess() { + return fProcess; + } + + public AbstractCLIProcess getCLIProcess() { + return fCLIProcess; + } + + public MIInferiorProcess getInferiorProcess() { + return fInferiorProcess; + } + + public boolean isGDBExited() { + return fMonitorJob != null && fMonitorJob.fExited; + } + + public int getGDBExitCode() { + return fGDBExitValue; + } + + public IPath getExecutablePath() { return fExecPath; } + + public void getInferiorExitCode(final DataRequestMonitor rm) { + fCommandCache.execute( + new MIGDBShowExitCode(fControlDmc), + new DataRequestMonitor(getExecutor(), rm) { + @Override + protected void handleOK() { + rm.setData(getData().getCode()); + rm.done(); + } + }); + } + + public void getInferiorProcessId(DataRequestMonitor rm) { + } + + @DsfServiceEventHandler + public void eventDispatched(ExitedEvent e) { + // Handle our "GDB Exited" event and stop processing commands. + stopCommandProcessing(); + } + + /** + * Monitors a system process, waiting for it to terminate, and + * then notifies the associated runtime process. + */ + private class MonitorJob extends Job { + boolean fExited = false; + DsfRunnable fMonitorStarted; + Process fMonProcess; + + @Override + protected IStatus run(IProgressMonitor monitor) { + synchronized(fMonProcess) { + getExecutor().submit(fMonitorStarted); + while (!fExited) { + try { + fMonProcess.waitFor(); + fGDBExitValue = fMonProcess.exitValue(); + } catch (InterruptedException ie) { + // clear interrupted state + Thread.interrupted(); + } finally { + fExited = true; + getSession().dispatchEvent(new ExitedEvent(fControlDmc) {}, getProperties()); + } + } + } + return Status.OK_STATUS; + } + + MonitorJob(Process process, DsfRunnable monitorStarted) { + super("GDB process monitor job."); //$NON-NLS-1$ + fMonProcess = process; + fMonitorStarted = monitorStarted; + setSystem(true); + } + + void kill() { + synchronized(fMonProcess) { + if (!fExited) { + getThread().interrupt(); + } + } + } + } + + public static class InitializationShutdownStep extends Sequence.Step { + public enum Direction { INITIALIZING, SHUTTING_DOWN } + + private Direction fDirection; + InitializationShutdownStep(Direction direction) { fDirection = direction; } + + @Override + final public void execute(RequestMonitor requestMonitor) { + if (fDirection == Direction.INITIALIZING) { + initialize(requestMonitor); + } else { + shutdown(requestMonitor); + } + } + + @Override + final public void rollBack(RequestMonitor requestMonitor) { + if (fDirection == Direction.INITIALIZING) { + shutdown(requestMonitor); + } else { + super.rollBack(requestMonitor); + } + } + + protected void initialize(RequestMonitor requestMonitor) { + requestMonitor.done(); + } + protected void shutdown(RequestMonitor requestMonitor) { + requestMonitor.done(); + } + } + + protected class GDBProcessStep extends InitializationShutdownStep { + GDBProcessStep(Direction direction) { super(direction); } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + class GDBLaunchMonitor { + boolean fLaunched = false; + boolean fTimedOut = false; + } + final GDBLaunchMonitor fGDBLaunchMonitor = new GDBLaunchMonitor(); + + final RequestMonitor gdbLaunchRequestMonitor = new RequestMonitor(getExecutor(), null) { + @Override + protected void handleCompleted() { + if (!fGDBLaunchMonitor.fTimedOut) { + fGDBLaunchMonitor.fLaunched = true; + if (!getStatus().isOK()) { + requestMonitor.setStatus(getStatus()); + } + requestMonitor.done(); + } + } + }; + + final Job startGdbJob = new Job("Start GDB Process Job") { //$NON-NLS-1$ + @Override + protected IStatus run(IProgressMonitor monitor) { + List commandList = new ArrayList(); + + commandList.add(fGdbPath.toOSString()); + if (fExecPath != null) { + commandList.add("--interpreter"); //$NON-NLS-1$ + commandList.add("mi"); //$NON-NLS-1$ + commandList.add(fExecPath.toOSString()); + } + + String[] commandLine = commandList.toArray(new String[commandList.size()]); + + try { + fProcess = ProcessFactory.getFactory().exec(commandLine); + } catch(IOException e) { + String message = MessageFormat.format("Error while launching command", //$NON-NLS-1$ + new Object[]{commandList.toString()}); + gdbLaunchRequestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, message, e)); + gdbLaunchRequestMonitor.done(); + return Status.OK_STATUS; + } + + try { + InputStream stream = fProcess.getInputStream(); + Reader r = new InputStreamReader(stream); + BufferedReader reader = new BufferedReader(r); + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + //System.out.println("GDB " + line); + if (line.endsWith("(gdb)")) { //$NON-NLS-1$ + break; + } + } + } catch (IOException e) { + gdbLaunchRequestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, "Error reading GDB STDOUT", e)); //$NON-NLS-1$ + gdbLaunchRequestMonitor.done(); + return Status.OK_STATUS; + } + + gdbLaunchRequestMonitor.done(); + return Status.OK_STATUS; + } + }; + startGdbJob.schedule(); + + getExecutor().schedule(new Runnable() { + public void run() { + // Only process the event if we have not finished yet (hit the breakpoint). + if (!fGDBLaunchMonitor.fLaunched) { + fGDBLaunchMonitor.fTimedOut = true; + Thread jobThread = startGdbJob.getThread(); + if (jobThread != null) { + jobThread.interrupt(); + } + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, "Timed out trying to launch GDB.", null)); //$NON-NLS-1$ + requestMonitor.done(); + } + }}, + fGDBLaunchTimeout, TimeUnit.SECONDS); + + } + + @Override + protected void shutdown(final RequestMonitor requestMonitor) { + new Job("Terminating GDB process.") { //$NON-NLS-1$ + @Override + protected IStatus run(IProgressMonitor monitor) { + if (fProcess == null) + fProcess.destroy(); + + int attempts = 0; + while (attempts < 10) { + try { + // Don't know if we really need the exit value... but what the hell. + fGDBExitValue = fProcess.exitValue(); // throws exception if process not exited + + requestMonitor.done(); + return Status.OK_STATUS; + } catch (IllegalThreadStateException ie) { + } + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + attempts++; + } + requestMonitor.setStatus(new Status( + IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfService.REQUEST_FAILED, "Process terminate failed", null)); //$NON-NLS-1$ + requestMonitor.done(); + return Status.OK_STATUS; + } + }.schedule(); + } + } + + protected class MonitorJobStep extends InitializationShutdownStep { + MonitorJobStep(Direction direction) { super(direction); } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + fMonitorJob = new MonitorJob( + fProcess, + new DsfRunnable() { + public void run() { + requestMonitor.done(); + } + }); + fMonitorJob.schedule(); + } + + @Override + protected void shutdown(RequestMonitor requestMonitor) { + if (!fMonitorJob.fExited) { + fMonitorJob.kill(); + } + requestMonitor.done(); + } + } + + protected class CommandMonitoringStep extends InitializationShutdownStep { + CommandMonitoringStep(Direction direction) { super(direction); } + + @Override + protected void initialize(final RequestMonitor requestMonitor) { + startCommandProcessing(fProcess.getInputStream(), fProcess.getOutputStream()); + requestMonitor.done(); + } + + @Override + protected void shutdown(RequestMonitor requestMonitor) { + stopCommandProcessing(); + requestMonitor.done(); + } + } + + protected class CheckInterpreterConsoleStep extends InitializationShutdownStep { + CheckInterpreterConsoleStep(Direction direction) { super(direction); } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + MIInterpreterExecConsole cmd = new MIInterpreterExecConsole(fControlDmc, "echo"); //$NON-NLS-1$ + GDBControl.this.queueCommand( + cmd, + new DataRequestMonitor(getExecutor(), null) { + @Override + protected void handleCompleted() { + fUseInterpreterConsole = getStatus().isOK(); + requestMonitor.done(); + } + } + ); + } + } + + protected class CommandProcessorsStep extends InitializationShutdownStep { + CommandProcessorsStep(Direction direction) { super(direction); } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + try { + fCLIProcess = new GDBCLIProcess(GDBControl.this, fUseInterpreterConsole); + } + catch(IOException e) { + requestMonitor.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfService.REQUEST_FAILED, "Failed to create CLI Process", e)); //$NON-NLS-1$ + requestMonitor.done(); + return; + } + + fInferiorProcess = new GDBInferiorProcess(GDBControl.this, fProcess.getOutputStream()); + fCLICommandProcessor = new CLIEventProcessor(GDBControl.this, fControlDmc, fInferiorProcess); + fMIEventProcessor = new MIRunControlEventProcessor(GDBControl.this, fControlDmc); + fCommandCache = new CommandCache(GDBControl.this); + + requestMonitor.done(); + } + + @Override + protected void shutdown(RequestMonitor requestMonitor) { + fCLICommandProcessor.dispose(); + fMIEventProcessor.dispose(); + fCLIProcess.dispose(); + fInferiorProcess.dispose(); + + requestMonitor.done(); + } + } + + protected class RegisterStep extends InitializationShutdownStep { + RegisterStep(Direction direction) { super(direction); } + @Override + public void initialize(final RequestMonitor requestMonitor) { + getSession().addServiceEventListener(GDBControl.this, null); + register(new String[]{ ICommandControl.class.getName(), AbstractMIControl.class.getName() }, new Hashtable()); + getSession().dispatchEvent(new StartedEvent(getGDBDMContext()), getProperties()); + requestMonitor.done(); + } + + @Override + protected void shutdown(RequestMonitor requestMonitor) { + unregister(); + getSession().removeServiceEventListener(GDBControl.this); + requestMonitor.done(); + } + } +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBControlDMContext.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBControlDMContext.java new file mode 100644 index 00000000000..808536dfd15 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBControlDMContext.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2007 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.gdb.service.command; + +import org.eclipse.dd.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; +import org.eclipse.dd.dsf.debug.service.IMemory.IMemoryDMContext; +import org.eclipse.dd.dsf.debug.service.IModules.ISymbolDMContext; +import org.eclipse.dd.dsf.debug.service.IRunControl.IContainerDMContext; +import org.eclipse.dd.dsf.debug.service.ISignals.ISignalsDMContext; +import org.eclipse.dd.dsf.debug.service.ISourceLookup.ISourceLookupDMContext; +import org.eclipse.dd.mi.service.command.MIControlDMContext; + +/** + * + */ +public class GDBControlDMContext extends MIControlDMContext + implements IContainerDMContext, ISymbolDMContext, IMemoryDMContext, IBreakpointsTargetDMContext, ISourceLookupDMContext, + ISignalsDMContext +{ + + public GDBControlDMContext(String sessionId, String commandControlId) { + super(sessionId, commandControlId); + } + +} diff --git a/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBInferiorProcess.java b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBInferiorProcess.java new file mode 100644 index 00000000000..d4c16ab68c7 --- /dev/null +++ b/plugins/org.eclipse.dd.gdb/src/org/eclipse/dd/gdb/service/command/GDBInferiorProcess.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2007 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.gdb.service.command; + +import java.io.OutputStream; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.RejectedExecutionException; + +import org.eclipse.cdt.utils.pty.PTY; +import org.eclipse.dd.dsf.concurrent.DsfRunnable; +import org.eclipse.dd.dsf.concurrent.ThreadSafeAndProhibitedFromDsfExecutor; +import org.eclipse.dd.gdb.service.command.GDBControl.SessionType; +import org.eclipse.dd.mi.service.command.MIInferiorProcess; + +/** + * + */ +class GDBInferiorProcess extends MIInferiorProcess { + + + public GDBInferiorProcess(GDBControl commandControl, PTY p) { + super(commandControl, p); + } + + public GDBInferiorProcess(GDBControl commandControl, OutputStream gdbOutputStream) { + super(commandControl, gdbOutputStream); + } + + @Override + @ThreadSafeAndProhibitedFromDsfExecutor("getSession#getExecutor") + public void destroy() { + try { + getSession().getExecutor().submit(new DsfRunnable() { + public void run() { + if (isDisposed() || !getSession().isActive()) return; + GDBControl gdb = (GDBControl)getCommandControl(); + if (gdb == null) return; + + // An inferior will be destroy():interrupt and kill if + // - For attach session: + // the inferior was not disconnected yet (no need to try + // to kill a disconnected program). + // - For Program session: + // if the inferior was not terminated. + // - For PostMortem(Core): send event + // else noop + if ((gdb.getSessionType() == SessionType.ATTACH && gdb.isConnected()) || + (gdb.getSessionType() == SessionType.RUN && getState() != State.TERMINATED)) + { + // Try to interrupt the inferior, first. + if (getState() == State.RUNNING) { + gdb.interrupt(); + } + } + } + }).get(); + } catch (RejectedExecutionException e) { + } catch (InterruptedException e) { + } catch (ExecutionException e) { + } finally { + super.destroy(); + } + } +}