Skip to main content
summaryrefslogtreecommitdiffstats
path: root/debug
diff options
context:
space:
mode:
authorJohn Cortell2011-04-14 16:31:22 -0400
committerJohn Cortell2011-04-14 16:31:22 -0400
commit9782f044c75004609e2eef2caf43dcff75a84012 (patch)
tree854b494ad83961cb08f97fc56c50d85aefffd6cd /debug
parent78cb17e7084bf121bac4b87d2caf098d7833be36 (diff)
downloadorg.eclipse.cdt-9782f044c75004609e2eef2caf43dcff75a84012.tar.gz
org.eclipse.cdt-9782f044c75004609e2eef2caf43dcff75a84012.tar.xz
org.eclipse.cdt-9782f044c75004609e2eef2caf43dcff75a84012.zip
Bug 342141 - Executables view content goes stale in various scenarios
Diffstat (limited to 'debug')
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/Executable.java31
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/ExecutablesManager.java793
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener.java56
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener2.java37
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardExecutableImporter.java2
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardSourceFilesProvider.java2
-rw-r--r--debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/srcfinder/CSourceFinder.java33
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java46
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java13
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java53
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java1
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties1
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java403
-rw-r--r--debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java122
14 files changed, 1071 insertions, 522 deletions
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/Executable.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/Executable.java
index 124596bb58..2cb2b47fdf 100644
--- a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/Executable.java
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/Executable.java
@@ -22,6 +22,7 @@ import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ITranslationUnit;
+import org.eclipse.cdt.debug.internal.core.Trace;
import org.eclipse.cdt.internal.core.model.CModelManager;
import org.eclipse.cdt.internal.core.model.ExternalTranslationUnit;
import org.eclipse.cdt.internal.core.model.TranslationUnit;
@@ -40,7 +41,29 @@ import org.eclipse.core.runtime.content.IContentTypeManager;
public class Executable extends PlatformObject {
+ /**
+ * Poorly named. This does not determine if the the file is an executable
+ * but rather a binary. Use {@link #isBinaryFile(IPath)} instead.
+ *
+ * @deprecated use {@link #isBinaryFile(IPath)}
+ */
+ @Deprecated
static public boolean isExecutableFile(IPath path) {
+ return isBinaryFile(path);
+ }
+
+ /**
+ * Determines if the given file is a binary file. For our purposes, an
+ * "executable" is a runnable program (an .exe file on Windows, e.g.,) or a
+ * shared library. A binary can be an executable but it can also be an
+ * instruction-containing artifact of a build, which typically is linked to
+ * make an executable (.e.,g .o and .obj files)
+ *
+ * @param path
+ * @return
+ * @since 7.1
+ */
+ static public boolean isBinaryFile(IPath path) {
// ignore directories
if (path.toFile().isDirectory()) {
return false;
@@ -159,9 +182,12 @@ public class Executable extends PlatformObject {
* @since 6.0
*/
public synchronized ITranslationUnit[] getSourceFiles(IProgressMonitor monitor) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null);
- if (!refreshSourceFiles && !remapSourceFiles)
+ if (!refreshSourceFiles && !remapSourceFiles) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "returning cached result"); //$NON-NLS-1$
return sourceFiles.toArray(new TranslationUnit[sourceFiles.size()]) ;
+ }
// Try to get the list of source files used to build the binary from the
// symbol information.
@@ -274,6 +300,8 @@ public class Executable extends PlatformObject {
* @since 6.0
*/
public void setRefreshSourceFiles(boolean refreshSourceFiles) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, refreshSourceFiles);
+
this.refreshSourceFiles = refreshSourceFiles;
}
@@ -309,6 +337,7 @@ public class Executable extends PlatformObject {
* @since 7.0
*/
public void setRemapSourceFiles(boolean remapSourceFiles) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, remapSourceFiles);
this.remapSourceFiles = remapSourceFiles;
}
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/ExecutablesManager.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/ExecutablesManager.java
index ff813480c1..eeab24b4c9 100644
--- a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/ExecutablesManager.java
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/ExecutablesManager.java
@@ -11,31 +11,36 @@
package org.eclipse.cdt.debug.core.executables;
-import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.eclipse.cdt.core.model.CoreModel;
+import org.eclipse.cdt.core.model.ElementChangedEvent;
+import org.eclipse.cdt.core.model.IBinary;
+import org.eclipse.cdt.core.model.ICElement;
+import org.eclipse.cdt.core.model.ICElementDelta;
+import org.eclipse.cdt.core.model.ICProject;
+import org.eclipse.cdt.core.model.IElementChangedListener;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.cdt.debug.core.CDebugCorePlugin;
+import org.eclipse.cdt.debug.internal.core.Trace;
import org.eclipse.cdt.debug.internal.core.executables.StandardExecutableImporter;
import org.eclipse.cdt.debug.internal.core.executables.StandardSourceFileRemappingFactory;
import org.eclipse.cdt.debug.internal.core.executables.StandardSourceFilesProvider;
+import org.eclipse.cdt.internal.core.model.CModelManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
-import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
-import org.eclipse.core.resources.IResourceChangeListener;
-import org.eclipse.core.resources.IResourceDelta;
-import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
@@ -52,9 +57,11 @@ import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.osgi.service.debug.DebugOptions;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationListener;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
/**
* The Executables Manager maintains a collection of executables built by all of
@@ -64,35 +71,119 @@ import org.osgi.framework.ServiceReference;
* @author Ken Ryall
*
*/
-public class ExecutablesManager extends PlatformObject implements IResourceChangeListener, ICProjectDescriptionListener {
+public class ExecutablesManager extends PlatformObject implements ICProjectDescriptionListener, IElementChangedListener {
private static final String EXECUTABLES_MANAGER_DEBUG_TRACING = CDebugCorePlugin.PLUGIN_ID + "EXECUTABLES_MANAGER_DEBUG_TRACING"; //$NON-NLS-1$
private Map<IProject, IProjectExecutablesProvider> executablesProviderMap = new HashMap<IProject, IProjectExecutablesProvider>();
- private Map<IProject, List<Executable>> executablesMap = new HashMap<IProject, List<Executable>>();
private List<IExecutablesChangeListener> changeListeners = Collections.synchronizedList(new ArrayList<IExecutablesChangeListener>());
private List<IProjectExecutablesProvider> executableProviders;
private List<ISourceFilesProvider> sourceFileProviders;
private List<ISourceFileRemappingFactory> sourceFileRemappingFactories;
private List<IExecutableImporter> executableImporters;
- private boolean DEBUG;
- private Job refreshJob = new Job("Get Executables") { //$NON-NLS-1$
+ /**
+ * Map of launch config names to the path locator memento string in the
+ * launch config, recorded in the most recent launch config change
+ * notification. We use this to ensure we flush source file mappings only
+ * when the launch config change involves a change to the source locators.
+ */
+ private Map<String, String> locatorMementos = new HashMap<String,String>();
+
+
+ /**
+ * A cache of the executables in the workspace, categorized by project.
+ *
+ * <p>
+ * This cache is updated by scheduling an asynchronous search. SearchJob is
+ * the only class that should <i>modify</i> this collection, including the
+ * sub collections of Executable objects. The collection can be read from
+ * any thread at any time. All access (read or write) must be serialized by
+ * synchronizing on the Map object.
+ * <p>
+ * The same Executable may appear more than once.
+ */
+ private Map<IProject, List<Executable>> executablesMap = new HashMap<IProject, List<Executable>>();
+
+ /**
+ * Provide a flat list of the executables in {@link #executablesMap}, with
+ * duplicates removed. That is effectively the list of all executables in
+ * the workspace that we know of as of now.
+ *
+ * @return
+ */
+ private List<Executable> flattenExecutablesMap() {
+ List<Executable> result = new ArrayList<Executable>(executablesMap.size() * 5); // most projects will have less than five executables
+ synchronized (executablesMap) {
+ for (List<Executable> exes : executablesMap.values()) {
+ for (Executable exe : exes) {
+ if (!result.contains(exe)) {
+ result.add(exe);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Job which searches through CDT projects for executables. Only one thread
+ * should be running this job at any one time. Running job should be
+ * cancelled and verified terminated before initiating another.
+ */
+ class SearchJob extends Job {
+ SearchJob() {
+ super("Executables Search"); //$NON-NLS-1$
+ }
+
+ /**
+ * The projects given to us when scheduled. If null, flush our entire
+ * cache and search all projects
+ */
+ private IProject[] projectsToRefresh;
+
@Override
public IStatus run(IProgressMonitor monitor) {
-
- trace("Get Executables job started at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Search for executables started"); //$NON-NLS-1$
+
+ IStatus status = Status.OK_STATUS;
+
+ // The executables we know of now. We'll compare the search results
+ // to this and see if we need to notify change listeners
+ List<Executable> before = flattenExecutablesMap();
- List<IProject> projects = getProjectsToCheck();
+ // Get the CDT projects in the workspace that we have no cached
+ // results for (are not in 'executablesMap'). Also, we may have been
+ // asked to refresh the cache for some projects we've search before
+ List<IProject> projects = new ArrayList<IProject>();
+ synchronized (executablesMap) {
+ if (projectsToRefresh == null) {
+ executablesMap.clear();
+ }
+ else {
+ for (IProject project : projectsToRefresh) {
+ executablesMap.remove(project);
+ }
+ }
+
+ // Get the list of projects we plan to search
+ for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
+ if (!executablesMap.containsKey(project) && CoreModel.hasCNature(project)) {
+ projects.add(project);
+ }
+ }
+ }
+
SubMonitor subMonitor = SubMonitor.convert(monitor, projects.size());
for (IProject project : projects) {
if (subMonitor.isCanceled()) {
- trace("Get Executables job cancelled at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
- return Status.CANCEL_STATUS;
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Search for executables canceled"); //$NON-NLS-1$
+ status = Status.CANCEL_STATUS;
+ break; // we've already changed our model; stop searching but proceed to notify listeners that the model changed
}
subMonitor.subTask("Checking project: " + project.getName()); //$NON-NLS-1$
@@ -100,36 +191,78 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
// get the executables provider for this project
IProjectExecutablesProvider provider = getExecutablesProviderForProject(project);
if (provider != null) {
- trace("Getting executables for project: " + project.getName() + " using " + provider.toString()); //$NON-NLS-1$//$NON-NLS-2$
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Getting executables for project: " + project.getName() + " using " + provider.toString()); //$NON-NLS-1$ //$NON-NLS-2$
List<Executable> executables = provider.getExecutables(project, subMonitor.newChild(1, SubMonitor.SUPPRESS_NONE));
// store the list of executables for this project
synchronized (executablesMap) {
- if (!monitor.isCanceled()) {
- executablesMap.put(project, executables);
- }
+ executablesMap.put(project, executables);
}
}
}
+
+ // See if, after all that work, there's a net change in the
+ // executables list. If so, notify listeners.
+ List<Executable> after = flattenExecutablesMap();
+ List<Executable> removed = before;
+ List<Executable> added = new ArrayList<Executable>(after.size());
+ for (Executable a : after) {
+ if (!removed.remove(a)) {
+ added.add(a);
+ }
+ }
// notify the listeners
synchronized (changeListeners) {
- for (IExecutablesChangeListener listener : changeListeners) {
- listener.executablesListChanged();
+ if (removed.size() > 0 || added.size() > 0) {
+ for (IExecutablesChangeListener listener : changeListeners) {
+ // New interface
+ if (listener instanceof IExecutablesChangeListener2) {
+ if (removed.size() > 0) {
+ ((IExecutablesChangeListener2)listener).executablesRemoved(removed);
+ }
+ if (added.size() > 0) {
+ ((IExecutablesChangeListener2)listener).executablesAdded(added);
+ }
+ }
+ // Old interface
+ listener.executablesListChanged();
+ }
}
}
- trace("Get Executables job finished at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Search for executables finished"); //$NON-NLS-1$
+
+ return status;
+ }
- return Status.OK_STATUS;
+ /**
+ * Schedules the search job. Use this, not the standard Job.schedule()
+ * method.
+ *
+ * @param projectsToRefresh
+ * if null, all CDT projects in the workspace are searched.
+ * If not null, we search only newly present projects and the
+ * projects provided (even if searched before). Empty list
+ * can be passed to search only newly present projects.
+ */
+ public void schedule(IProject[] projectsToRefresh) {
+ this.projectsToRefresh = projectsToRefresh;
+ super.schedule();
}
};
+
+ /** The search job. We only let one of these run at any one time */
+ private SearchJob searchJob = new SearchJob();
+
+ /** Lock used to serialize the search jobs */
+ private Object searchSchedulingLock = new Object();
- private static ExecutablesManager executablesManager = null;
+ /** The singleton */
+ private static ExecutablesManager executablesManager;
/**
- * Get the executables manager instance
- * @return the executables manager
+ * @return the singleton manager
*/
public static ExecutablesManager getExecutablesManager() {
if (executablesManager == null)
@@ -138,25 +271,7 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
}
public ExecutablesManager() {
-
- // check if debugging is enabled
- BundleContext context = CDebugCorePlugin.getDefault().getBundle().getBundleContext();
- if (context != null) {
- ServiceReference reference = CDebugCorePlugin.getDefault().getBundle().getBundleContext().getServiceReference(DebugOptions.class.getName());
- if (reference != null) {
- DebugOptions service = (DebugOptions) context.getService(reference);
- if (service != null) {
- try {
- DEBUG = service.getBooleanOption(EXECUTABLES_MANAGER_DEBUG_TRACING, false);
- } finally {
- // we have what we want - release the service
- context.ungetService(reference);
- }
- }
- }
- }
-
- refreshJob.setPriority(Job.SHORT);
+ searchJob.setPriority(Job.SHORT);
// load the extension points
loadExecutableProviderExtensions();
@@ -171,12 +286,101 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
executableImporters.add(0, new StandardExecutableImporter());
// listen for events we're interested in
- ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.POST_BUILD);
+ CModelManager.getDefault().addElementChangedListener(this);
CoreModel.getDefault().getProjectDescriptionManager().addCProjectDescriptionListener(this,
CProjectDescriptionEvent.APPLIED);
+
+ // Listen for changes to the global source locators. These locators
+ // affect how source files are found locally. The Executable objects
+ // cache their local source file paths and rely on us to tell them to
+ // flush those caches when applicable locators change.
+ CDebugCorePlugin.getDefault().getCommonSourceLookupDirector().addParticipants(new ISourceLookupParticipant[] { new ISourceLookupParticipant(){
+
+ public void init(ISourceLookupDirector director) {}
+ public Object[] findSourceElements(Object object) { return new Object[0]; }
+ public String getSourceName(Object object) throws CoreException { return ""; } //$NON-NLS-1$
+ public void dispose() {}
+ public void sourceContainersChanged(ISourceLookupDirector director) {
+ // Unfortunately, it would be extremely difficult/costly to
+ // determine which binaries are effected by the source locator
+ // change, so we have to tell all Executables to flush
+ flushExecutablesSourceMappings();
+ }
+ } });
+
+ // Source locators are also in launch configurations, and those too come
+ // into play when an Executable looks for a source file locally. So,
+ // listen for changes in those locators, too.
+ DebugPlugin.getDefault().getLaunchManager().addLaunchConfigurationListener(new ILaunchConfigurationListener() {
+ public void launchConfigurationChanged(ILaunchConfiguration configuration) {
+ // Expect lots of noise for working copies. We only care about
+ // changes to actual configs
+ if (configuration.isWorkingCopy()) {
+ return;
+ }
+
+ // If the source locators in the launch config were not modified, then no-op
+ try {
+ String configName = configuration.getName();
+ String mementoBefore = locatorMementos.get(configName);
+ String mementoNow = configuration.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, ""); //$NON-NLS-1$
+ if (mementoNow.equals(mementoBefore)) {
+ return; // launch config change had no affect on source locators
+ }
+ locatorMementos.put(configName, mementoNow);
+ } catch (CoreException e) {
+ CDebugCorePlugin.log(e);
+ }
+
+ // TODO: For now, just tell all Executables to flush. Look
+ // into identifying which binary the config is associated
+ // with so we can flush only that Executable
+ flushExecutablesSourceMappings();
+ }
+ public void launchConfigurationRemoved(ILaunchConfiguration configuration) { configAddedOrRemoved(configuration); }
+ public void launchConfigurationAdded(ILaunchConfiguration configuration) { configAddedOrRemoved(configuration); }
+ private void configAddedOrRemoved(ILaunchConfiguration configuration) {
+ // Expect lots of noise for working copies. We only care about
+ // changes to actual configs
+ if (configuration.isWorkingCopy()) {
+ return;
+ }
+
+ // The addition or removal of a launch config could affect
+ // how files are found. It would be extremely costly to
+ // determine here whether it will or not, so assume it will.
+
+ // TODO: For now, just tell all Executables to flush. Look
+ // into identifying which binary the config is associated
+ // with so we can flush only that Executable
+ flushExecutablesSourceMappings();
+ }
+ });
// schedule a refresh so we get up to date
- scheduleRefresh();
+ scheduleExecutableSearch(null);
+ }
+
+ /**
+ * Tell all Executable objects to flush their source file mappings, then
+ * notify our listeners that the executables changed. Even though the
+ * binaries may not have actually changed, the impact to a client of
+ * Executable is the same. If the client has cached any of the source file
+ * information the Executable provided, that info can no longer be trusted.
+ * The primary purpose of an Executable is to provide source file path
+ * information--not only the compile paths burned into the executable but
+ * also the local mappings of those paths.
+ */
+ private void flushExecutablesSourceMappings() {
+ List<Executable> exes = flattenExecutablesMap();
+ for (Executable exe : exes) {
+ exe.setRemapSourceFiles(true);
+ }
+ synchronized (changeListeners) {
+ for (IExecutablesChangeListener listener : changeListeners) {
+ listener.executablesChanged(exes);
+ }
+ }
}
/**
@@ -196,44 +400,38 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
}
/**
- * Gets the list of executables in the workspace.
- * @param wait whether or not to wait if the list is being refreshed when this
- * method is called. when true, this call will not return until the list is
- * complete. when false, it will return with the last known list. if calling
- * from any UI, you should not block the UI waiting for this to return, but rather
- * register as an {@link IExecutablesChangeListener} to get notifications when the
- * list changes.
- * @return the list of executables which may be empty
+ * Gets the list of executables in the workspace. This method doesn't
+ * initiate a search. It returns the cached results of the most recent
+ * search, or waits for the ongoing search to complete.
+ *
+ * @param wait
+ * Whether or not to wait if the cache is in the process of being
+ * updated when this method is called. When true, the call will
+ * block until the update is complete. When false, it will return
+ * the current cache. Callers on the UI thread should pass false
+ * to avoid temporarily freezing the UI. Note that clients can
+ * register as a {@link IExecutablesChangeListener} or
+ * {@link IExecutablesChangeListener2}to get notifications when
+ * the cache changes.
+ * @return the list of executables; may be empty. List will not have
+ * duplicates.
* @since 7.0
*/
public Collection<Executable> getExecutables(boolean wait) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, Boolean.valueOf(wait));
- trace("getExecutables called at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
-
- List<Executable> executables = new ArrayList<Executable>();
-
- if (wait && refreshJob.getState() != Job.NONE) {
- trace("waiting for refresh job to finish at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
+ // Wait for running search to finish, if asked to
+ if (wait && searchJob.getState() != Job.NONE) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Waiting for executable search to finish..."); //$NON-NLS-1$
try {
- refreshJob.join();
+ searchJob.join();
} catch (InterruptedException e) {
}
- trace("refresh job finished at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "...executable search finished."); //$NON-NLS-1$
}
- synchronized (executablesMap) {
- for (List<Executable> exes : executablesMap.values()) {
- for (Executable exe : exes) {
- if (!executables.contains(exe)) {
- executables.add(exe);
- }
- }
- }
- }
-
- trace("getExecutables returned at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
-
- return executables;
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceExit(null);
+ return flattenExecutablesMap();
}
/**
@@ -300,7 +498,7 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
}
if (handled)
- scheduleRefresh();
+ scheduleExecutableSearch(null);
}
/**
@@ -329,10 +527,10 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
* @return an array of source files which may be empty
*/
public String[] getSourceFiles(final Executable executable, IProgressMonitor monitor) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executable);
+
String[] result = new String[0];
- trace("getSourceFiles called at " + getStringFromTimestamp(System.currentTimeMillis()) + " for " + executable.getPath().toOSString()); //$NON-NLS-1$//$NON-NLS-2$
-
synchronized (sourceFileProviders) {
Collections.sort(sourceFileProviders, new Comparator<ISourceFilesProvider>() {
@@ -351,17 +549,14 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
String[] sourceFiles = provider.getSourceFiles(executable, new SubProgressMonitor(monitor, 1000));
if (sourceFiles.length > 0) {
result = sourceFiles;
-
- trace("getSourceFiles got " + sourceFiles.length + " files from " + provider.toString()); //$NON-NLS-1$ //$NON-NLS-2$
-
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Got " + sourceFiles.length + " files from " + provider.toString()); //$NON-NLS-1$ //$NON-NLS-2$
break;
}
}
monitor.done();
}
- trace("getSourceFiles returned at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
-
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceExit(null, result);
return result;
}
@@ -382,221 +577,119 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
IProjectExecutablesProvider provider = getExecutablesProviderForProject(executable.getProject());
if (provider != null) {
IStatus result = provider.removeExecutable(executable, new SubProgressMonitor(monitor, 1));
- if (result.isOK()) {
- // remove the exe from the list
- List<Executable> exes = executablesMap.get(executable.getProject());
- if (exes != null) {
- exes.remove(executable);
- }
- } else {
+ if (!result.isOK()) {
status.add(result);
}
}
}
-
- // notify listeners that the list has changed. only do this if at least one delete succeeded.
- if (status.getChildren().length != executables.length) {
- synchronized (changeListeners) {
- for (IExecutablesChangeListener listener : changeListeners) {
- listener.executablesListChanged();
- }
- }
- }
+
+ // We don't need to directly call our listeners. The file removal will
+ // cause a C model change, which we will react to by then calling the
+ // listeners
return status;
}
/**
- * Refresh the list of executables for the given projects
- * @param projects the list of projects, or null. if null or the list
- * is empty, all projects will be refreshed.
+ * Initiates an asynchronous search of workspace CDT projects for
+ * executables. If a search is ongoing, it's cancelled and a new one is
+ * started. In all cases, this method returns quickly (does not wait/block).
+ *
+ * <p>
+ * Listeners are notified when the search is complete and there is a change
+ * in the collection of found executables. The results of the search can be
+ * obtained by calling {@link #getExecutables(boolean)}.
+ *
+ * @param projectsToRefresh
+ * if null, we discard our entire Executables cache and search
+ * all CDT projects in the workspace. If not null, we purge our
+ * cache for only the given projects then search in all CDT
+ * projects for which we have no cache. Passing a project that we
+ * have no cache for is innocuous. In all cases, we search for
+ * executables in any newly available projects. This parameter is
+ * simply a way to get us to <i>not</i> skip one or more projects
+ * we already have the executables list for.
+ *
* @since 7.0
*/
- public void refresh(List<IProject> projects) {
- if (projects == null || projects.size() == 0) {
- // clear the entire cache
- executablesMap.clear();
- } else {
- for (IProject project : projects) {
- executablesMap.remove(project);
- }
- }
-
- scheduleRefresh();
+ public void refresh(List<IProject> projectsToRefresh) {
+ scheduleExecutableSearch(projectsToRefresh != null ? projectsToRefresh.toArray(new IProject[projectsToRefresh.size()]) : null);
}
/**
* @since 7.0
+ * @deprecated we no longer listen directly for platform resource changes
+ * but rather C model changes
*/
- public void resourceChanged(IResourceChangeEvent event) {
-
- synchronized (executablesMap) {
- // project needs to be refreshed after a build/clean as the binary may
- // be added/removed/renamed etc.
- if (event.getType() == IResourceChangeEvent.POST_BUILD) {
- Object obj = event.getSource();
- if (obj != null && obj instanceof IProject) {
- try {
- // make sure there's at least one builder for the project. this gets called even
- // when there are no builder (e.g. the Executables project for imported executables).
- IProject project = (IProject)obj;
- if (project.getDescription().getBuildSpec().length > 0) {
- if (executablesMap.containsKey(obj)) {
- List<Executable> executables = executablesMap.remove(obj);
-
- trace("Scheduling refresh because project " + ((IProject)obj).getName() + " built or cleaned"); //$NON-NLS-1$//$NON-NLS-2$
-
- scheduleRefresh();
-
- // notify the listeners that these executables have possibly changed
- if (executables != null && executables.size() > 0) {
- synchronized (changeListeners) {
- for (IExecutablesChangeListener listener : changeListeners) {
- listener.executablesChanged(executables);
- }
- }
- }
- }
- }
- } catch (CoreException e) {
- e.printStackTrace();
- }
- }
- return;
- }
-
- // refresh when projects are opened or closed. note that deleted
- // projects are handled later in this method. new projects are handled
- // in handleEvent. resource changed events always start at the workspace
- // root, so projects are the next level down
- boolean refreshNeeded = false;
- IResourceDelta[] projects = event.getDelta().getAffectedChildren();
- for (IResourceDelta projectDelta : projects) {
- if ((projectDelta.getFlags() & IResourceDelta.OPEN) != 0) {
- if (projectDelta.getKind() == IResourceDelta.CHANGED) {
- // project was opened or closed
- if (executablesMap.containsKey(projectDelta.getResource())) {
- executablesMap.remove(projectDelta.getResource());
- }
- refreshNeeded = true;
- }
- }
- }
-
- if (refreshNeeded) {
- trace("Scheduling refresh because project(s) opened or closed"); //$NON-NLS-1$
-
- scheduleRefresh();
- return;
- }
-
- try {
- event.getDelta().accept(new IResourceDeltaVisitor() {
-
- public boolean visit(IResourceDelta delta) throws CoreException {
- if (delta.getKind() == IResourceDelta.ADDED || delta.getKind() == IResourceDelta.REMOVED) {
- IResource deltaResource = delta.getResource();
- if (deltaResource != null) {
- boolean refresh = false;
- if (delta.getKind() == IResourceDelta.REMOVED && deltaResource instanceof IProject) {
- // project deleted
- if (executablesMap.containsKey(deltaResource)) {
- executablesMap.remove(deltaResource);
- refresh = true;
-
- trace("Scheduling refresh because project " + deltaResource.getName() + " deleted"); //$NON-NLS-1$//$NON-NLS-2$
- }
- } else {
- // see if a binary has been added/removed
- IPath resourcePath = deltaResource.getLocation();
- if (resourcePath != null && Executable.isExecutableFile(resourcePath)) {
- if (executablesMap.containsKey(deltaResource.getProject())) {
- executablesMap.remove(deltaResource.getProject());
- refresh = true;
-
- trace("Scheduling refresh because a binary was added/removed"); //$NON-NLS-1$
- }
- }
- }
-
- if (refresh) {
- scheduleRefresh();
- return false;
- }
- }
- }
- return true;
- }
- });
- } catch (CoreException e) {
- }
- }
- }
+ @Deprecated
+ public void resourceChanged(IResourceChangeEvent event) {}
/**
* @since 7.0
*/
public void handleEvent(CProjectDescriptionEvent event) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, event);
+
// this handles the cases where the active build configuration changes,
// and when new projects are created or loaded at startup.
- boolean refresh = false;
-
int eventType = event.getEventType();
if (eventType == CProjectDescriptionEvent.APPLIED) {
- synchronized (executablesMap) {
- // see if the active build config has changed
- ICProjectDescription newDesc = event.getNewCProjectDescription();
- ICProjectDescription oldDesc = event.getOldCProjectDescription();
- if (oldDesc != null && newDesc != null) {
- String newConfigName = newDesc.getActiveConfiguration().getName();
- String oldConfigName = oldDesc.getActiveConfiguration().getName();
- if (!newConfigName.equals(oldConfigName)) {
- if (executablesMap.containsKey(newDesc.getProject())) {
- executablesMap.remove(newDesc.getProject());
- refresh = true;
-
- trace("Scheduling refresh because active build configuration changed"); //$NON-NLS-1$
- }
- }
- } else if (newDesc != null && oldDesc == null) {
- // project just created
- refresh = true;
-
- trace("Scheduling refresh because project " + newDesc.getProject().getName() + " created"); //$NON-NLS-1$//$NON-NLS-2$
+ // see if the active build config has changed
+ ICProjectDescription newDesc = event.getNewCProjectDescription();
+ ICProjectDescription oldDesc = event.getOldCProjectDescription();
+ if (oldDesc != null && newDesc != null) {
+ String newConfigName = newDesc.getActiveConfiguration().getName();
+ String oldConfigName = oldDesc.getActiveConfiguration().getName();
+ if (!newConfigName.equals(oldConfigName)) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Scheduling refresh because active build configuration changed"); //$NON-NLS-1$
+ scheduleExecutableSearch(new IProject[]{newDesc.getProject()});
}
+ } else if (newDesc != null && oldDesc == null) {
+ // project just created
+ scheduleExecutableSearch(null);
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Scheduling refresh because project " + newDesc.getProject().getName() + " created"); //$NON-NLS-1$//$NON-NLS-2$
}
}
-
- if (refresh) {
- scheduleRefresh();
- }
}
- private List<IProject> getProjectsToCheck() {
-
- List<IProject> projects = new ArrayList<IProject>();
-
- synchronized (executablesMap) {
- // look for any CDT projects not in our cache
- for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
- if (!executablesMap.containsKey(project)) {
- if (CoreModel.hasCNature(project)) {
- projects.add(project);
+ /**
+ * Initiates an asynchronous search of workspace CDT projects for
+ * executables. For details, see {@link #refresh(List)}, which is a public
+ * wrapper for this internal method. This method is more aptly named and
+ * takes an array instead of a list
+ */
+ private void scheduleExecutableSearch(final IProject[] projectsToRefresh) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, projectsToRefresh);
+
+ // Don't schedule multiple search jobs simultaneously. If one is
+ // running, cancel it, wait for it to terminate, then schedule a new
+ // one. However we must not block our caller, so spawn an intermediary
+ // thread to do that leg work. This isn't an efficient design, but these
+ // searches aren't done in high volume.
+ Job job = new Job("Executable search scheduler") { //$NON-NLS-1$
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ synchronized (searchSchedulingLock) {
+ searchJob.cancel();
+ if (searchJob.getState() != Job.NONE) {
+ try {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Waiting for canceled job to terminate"); //$NON-NLS-1$
+ searchJob.join();
+ } catch (InterruptedException e) {
+ }
}
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Scheduling new search job"); //$NON-NLS-1$
+ searchJob.schedule(projectsToRefresh);
}
+
+ return Status.OK_STATUS;
}
- }
-
- return projects;
- }
-
- private void scheduleRefresh() {
- trace("scheduleRefresh called at " + getStringFromTimestamp(System.currentTimeMillis())); //$NON-NLS-1$
-
- refreshJob.cancel();
- refreshJob.schedule();
+
+ };
+ job.setPriority(Job.SHORT);
+ job.schedule();
}
private IProjectExecutablesProvider getExecutablesProviderForProject(IProject project) {
@@ -762,14 +855,168 @@ public class ExecutablesManager extends PlatformObject implements IResourceChang
}
}
- private void trace(String msg) {
- if (DEBUG) {
- // TODO use Logger?
- System.out.println(msg);
+ /**
+ * We listen to C model changes and see if they affect what executables are
+ * in the workspace, and/or if the executables we already know of have
+ * changed.
+ *
+ * @since 7.1
+ */
+ public void elementChanged(ElementChangedEvent event) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null);
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "event = \n" + event); // must be done separately because of traceEntry() limitation //$NON-NLS-1$
+
+ // Examine the event and figure out what needs to be done
+ Set<IProject> refreshProjects = new HashSet<IProject>(5);
+ Set<Executable> executablesChanged = new HashSet<Executable>(5);
+ Set<Executable> executablesRemoved = new HashSet<Executable>(5);
+ processDeltas(event.getDelta().getAddedChildren(), null, refreshProjects, executablesRemoved, executablesChanged);
+ processDeltas(event.getDelta().getChangedChildren(), null, refreshProjects, executablesRemoved, executablesChanged);
+ processDeltas(event.getDelta().getRemovedChildren(), null, refreshProjects, executablesRemoved, executablesChanged);
+
+ // Schedule executable searches in projects
+ if (refreshProjects.size() > 0) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "One or more projects need to be re-searched"); //$NON-NLS-1$
+ scheduleExecutableSearch(refreshProjects.toArray(new IProject[refreshProjects.size()]));
+ }
+
+ // Invalidate the source file cache in changed Executables and inform
+ // listeners
+ if (executablesChanged.size() > 0) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "One or more executables changed"); //$NON-NLS-1$
+ for (Executable exec : executablesChanged) {
+ exec.setRefreshSourceFiles(true);
+ }
+ List<Executable> list = Arrays.asList(executablesChanged.toArray(new Executable[executablesChanged.size()]));
+ synchronized (changeListeners) {
+ for (IExecutablesChangeListener listener : changeListeners) {
+ listener.executablesChanged(list);
+ }
+ }
}
+ if (executablesRemoved.size() > 0) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "One or more executables were removed"); //$NON-NLS-1$
+ List<Executable> list = Arrays.asList(executablesRemoved.toArray(new Executable[executablesRemoved.size()]));
+ synchronized (changeListeners) {
+ for (IExecutablesChangeListener listener : changeListeners) {
+ if (listener instanceof IExecutablesChangeListener2) {
+ ((IExecutablesChangeListener2)listener).executablesRemoved(list);
+ }
+ }
+ }
+ }
+
+
+ return;
}
- private String getStringFromTimestamp(long timestamp) {
- return DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(timestamp));
+ /**
+ * Drills down a hierarchy of CDT model change events to determine the
+ * course of action.
+ *
+ * @param deltas
+ * CDT model events received by the viewer
+ * @param cproject
+ * the project the resources in [deltas] belong to
+ * @param projectsToRefresh
+ * implementation populates (appends) this list with the projects
+ * that need to be searched for executables. Note that Executable
+ * objects are created by an async job. The best we can do here
+ * is identify the projects that need to be searched. We can't
+ * provide a list of added Executables objects since they haven't
+ * been created yet.
+ * @param removedExecutables
+ * implementation populates (appends) this list with the
+ * Executable objects that have been removed, requiring listeners
+ * to be notified.
+ * @param changedExecutables
+ * implementation populates (appends) this list with the
+ * Executable objects that have changed, requiring listeners to
+ * be notified.
+ */
+ private void processDeltas(ICElementDelta[] deltas, ICProject cproject, final Set<IProject> projectsToRefresh, final Set<Executable> removedExecutables, final Set<Executable> changedExecutables) {
+ for (ICElementDelta delta : deltas) {
+ ICElement element = delta.getElement();
+ if (element instanceof ICProject) {
+ // When a project is deleted, we get a REMOVED delta for the
+ // project only--none for the elements in the project.
+ IProject project = ((ICProject)element).getProject();
+ if (delta.getKind() == ICElementDelta.REMOVED) {
+ projectsToRefresh.add(project);
+ List<Executable> execs = null;
+ synchronized (executablesMap) {
+ execs = executablesMap.get(project);
+ }
+ if (execs != null) {
+ for (Executable exec : execs) {
+ if (exec.getResource().equals(delta.getElement().getResource())) {
+ removedExecutables.add(exec);
+ break;
+ }
+ }
+
+ }
+ // Note that it's not our job to update 'executablesMap'.
+ // The async exec search job will do that.
+ }
+ }
+ else if (element instanceof IBinary) {
+ IProject project = cproject.getProject();
+ switch (delta.getKind()) {
+ case ICElementDelta.ADDED:
+ projectsToRefresh.add(project);
+ break;
+ case ICElementDelta.REMOVED: {
+ projectsToRefresh.add(project);
+ List<Executable> execs = null;
+ synchronized (executablesMap) {
+ execs = executablesMap.get(project);
+ }
+ if (execs != null) {
+ for (Executable exec : execs) {
+ if (exec.getResource().equals(delta.getElement().getResource())) {
+ removedExecutables.add(exec);
+ break;
+ }
+ }
+ }
+ // Note that it's not our job to update 'executablesMap'.
+ // The async exec search job will do that.
+ break;
+ }
+
+ case ICElementDelta.CHANGED: {
+ List<Executable> execs = null;
+ synchronized (executablesMap) {
+ execs = executablesMap.get(project);
+ }
+ if (execs == null) {
+ // Somehow, we missed the addition of the
+ // project. Request that the project be
+ // searched for executables
+ projectsToRefresh.add(project);
+ }
+ else {
+ // See if it's one of the executables we
+ // already know is in the project. If
+ // so, then we just need to tell
+ // listeners the executable changed
+ for (Executable exec : execs) {
+ if (exec.getResource().equals(delta.getElement().getResource())) {
+ changedExecutables.add(exec);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (element instanceof ICProject) {
+ cproject = (ICProject)element;
+ }
+ // recursively call ourselves to handle this delta's children
+ processDeltas(delta.getAffectedChildren(), cproject, projectsToRefresh, removedExecutables, changedExecutables);
+ }
}
}
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener.java
index 25b18c59d5..0245dbcc62 100644
--- a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener.java
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener.java
@@ -14,19 +14,65 @@ package org.eclipse.cdt.debug.core.executables;
import java.util.EventListener;
import java.util.List;
+/**
+ * Listener interface for finding out when the list of Executable objects in the
+ * workspace changes or when the objects themselves change.
+ *
+ * <p>
+ * Executable objects are ephemeral representations of Eclipse workspace model
+ * elements. A particular executable in the workspace is typically represented
+ * by many Executable objects. For example, an executable in the workspace that
+ * changes twice can cause the listener's {@link #executablesChanged(List)} to
+ * be called with a different Executable instance each of the two times it's invoked.
+ *
+ */
public interface IExecutablesChangeListener extends EventListener {
/**
- * Called whenever the list of executables in the workspace changes, e.g. a
- * project was opened/closed/created/deleted
+ * Called whenever the list of executables in the workspace changes. Many
+ * types of operations cause the list to change, for example:
+ * <ul>
+ * <li>project is built for the first time
+ * <li>project with executables already in place is open, closed, removed or
+ * cleaned
+ * <li>user deletes one or more executables
+ * </ul>
+ *
+ * Clients can get the list by calling {@link ExecutablesManager#getExecutables()}
+ *
* @since 7.0
*/
public void executablesListChanged();
/**
- * Called whenever some executables have changed, e.g. when a project is rebuilt or
- * cleaned. The content may have changed for example, so the list of source files
- * may be different.
+ * Called whenever one or more executables have changed, e.g. when a project
+ * is rebuilt. This is sometimes also called if the executable has not
+ * changed (i.e., the file on disk) but the information the Executable
+ * object provides has changed. One such case is when there's a change in
+ * the source locators, as such locators guide the Executable in finding the
+ * local path for the compile path.
+ *
+ * <p>
+ * The Executable instances in the given list have had their caches flushed
+ * by ExecutableManager. Clients that keep references to Executable objects
+ * must keep in mind that those particular instances may no longer be
+ * managed by ExecutableManager and as such it is the client's
+ * responsibility to tell those instances to flush when this listener method
+ * is called. E.g.,
+ *
+ * <p><pre>
+ * public void executablesChanged(List<Executable> executables) {
+ * for (Executable e : executables) {
+ * if (e.equals(fExecutable) {
+ * fExecutable.setRefreshSourceFiles(true);
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * This is not called when an executable is added or removed
+ *
* @param executables
* @since 7.0
*/
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener2.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener2.java
new file mode 100644
index 0000000000..87e3540bac
--- /dev/null
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/core/executables/IExecutablesChangeListener2.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Freescale Semiconductor 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:
+ * Freescale Semiconductor - Initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.cdt.debug.core.executables;
+
+import java.util.List;
+
+
+/**
+ * Extension of IExecutablesChangeListener which allows listeners to more
+ * precisely find out when an Executable is added or removed from the workspace
+ *
+ * @since 7.1
+ */
+public interface IExecutablesChangeListener2 extends IExecutablesChangeListener {
+
+ /**
+ * Called when one or more Executable objects have been added to the
+ * workspace
+ */
+ public void executablesAdded(List<Executable> executables);
+
+ /**
+ * Called when one or more Executable objects have been removed from the
+ * workspace
+ */
+ public void executablesRemoved(List<Executable> executables);
+
+} \ No newline at end of file
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardExecutableImporter.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardExecutableImporter.java
index 7325d0b084..b1761fa209 100644
--- a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardExecutableImporter.java
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardExecutableImporter.java
@@ -167,7 +167,7 @@ public class StandardExecutableImporter implements IExecutableImporter {
}
private void ensureBinaryType(IPath exectuableFilePath) {
- if (Executable.isExecutableFile(exectuableFilePath))
+ if (Executable.isBinaryFile(exectuableFilePath))
return;
String ext = exectuableFilePath.getFileExtension();
if (ext != null) {
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardSourceFilesProvider.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardSourceFilesProvider.java
index b231cdd0b0..4bf2b89165 100644
--- a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardSourceFilesProvider.java
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/executables/StandardSourceFilesProvider.java
@@ -45,7 +45,7 @@ public class StandardSourceFilesProvider extends PlatformObject implements ISour
return null;
}
- if (!Executable.isExecutableFile(executable.getPath()))
+ if (!Executable.isBinaryFile(executable.getPath()))
return null;
File f = new File(path.toOSString());
diff --git a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/srcfinder/CSourceFinder.java b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/srcfinder/CSourceFinder.java
index 30ce03e2e6..6ffb0fae50 100644
--- a/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/srcfinder/CSourceFinder.java
+++ b/debug/org.eclipse.cdt.debug.core/src/org/eclipse/cdt/debug/internal/core/srcfinder/CSourceFinder.java
@@ -69,10 +69,11 @@ public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListene
* launch config. This is a heavy operation. As an optimization, we cache
* the locators we create and discard when the launch config changes or is
* disposed. Collection is subject to be changed by listener invocations.
+ * Map key is the launch configuration name.
*
* @see CSourceFinder#getLocator(ILaunchConfiguration)
*/
- private Map<ILaunchConfiguration, ISourceLocator> fConfigLocators = Collections.synchronizedMap(new HashMap<ILaunchConfiguration, ISourceLocator>());
+ private Map<String, ISourceLocator> fConfigLocators = Collections.synchronizedMap(new HashMap<String, ISourceLocator>());
/**
* We use this when we don't have an ILaunch or ILaunchConfiguration
@@ -134,13 +135,15 @@ public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListene
if (fLaunchLocator == null) {
for (ILaunchConfiguration config : lmgr.getLaunchConfigurations()) {
if (isMatch(config)) {
+ String configName = config.getName();
+
// Search our cache of locators that we
// instantiate for configurations. Create one if
// not found
- ISourceLocator configLocator = fConfigLocators.get(config);
+ ISourceLocator configLocator = fConfigLocators.get(configName);
if (configLocator == null) {
configLocator = getLocator(config); // heavy operation
- fConfigLocators.put(config, configLocator); // cache to avoid next time
+ fConfigLocators.put(configName, configLocator); // cache to avoid next time
}
// In practice, a config's locator is always an ISourceLookupDirector
if (configLocator instanceof ISourceLookupDirector) {
@@ -330,14 +333,15 @@ public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListene
/* (non-Javadoc)
* @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationChanged(org.eclipse.debug.core.ILaunchConfiguration)
*/
- public void launchConfigurationChanged(ILaunchConfiguration config) {
+ synchronized public void launchConfigurationChanged(ILaunchConfiguration config) {
// We don't care if it's a working copy.
if (config.isWorkingCopy()) {
return;
}
+
// the source locator attribute may have changed
- fConfigLocators.remove(config);
- if ((fLaunchLocator != null) && (fLaunchLocator.getLaunchConfiguration() == config)) {
+ fConfigLocators.remove(config.getName());
+ if ((fLaunchLocator != null) && (fLaunchLocator.getLaunchConfiguration().getName() == config.getName())) {
fLaunchLocator = null;
}
}
@@ -345,9 +349,14 @@ public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListene
/* (non-Javadoc)
* @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationRemoved(org.eclipse.debug.core.ILaunchConfiguration)
*/
- public void launchConfigurationRemoved(ILaunchConfiguration config) {
- fConfigLocators.remove(config);
- if ((fLaunchLocator != null) && (fLaunchLocator.getLaunchConfiguration() == config)) {
+ synchronized public void launchConfigurationRemoved(ILaunchConfiguration config) {
+ // We don't care if it's a working copy.
+ if (config.isWorkingCopy()) {
+ return;
+ }
+
+ fConfigLocators.remove(config.getName());
+ if ((fLaunchLocator != null) && (fLaunchLocator.getLaunchConfiguration().getName() == config.getName())) {
fLaunchLocator = null;
}
}
@@ -367,7 +376,7 @@ public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListene
/* (non-Javadoc)
* @see org.eclipse.debug.core.ILaunchesListener#launchesAdded(org.eclipse.debug.core.ILaunch[])
*/
- synchronized public void launchesAdded(ILaunch[] launches) {
+ public void launchesAdded(ILaunch[] launches) {
// If there's a new launch in town, we need to take it into
// consideration. E.g., if it targets our binary, and we're currently
// searching using an inactive launch configuration's locator, then the
@@ -375,7 +384,9 @@ public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListene
for (ILaunch launch : launches) {
ILaunchConfiguration config = launch.getLaunchConfiguration();
if (config != null && isMatch(config)) {
- fLaunchLocator = null;
+ synchronized(this) {
+ fLaunchLocator = null;
+ }
}
}
}
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java
index 18e7ced7a4..132c4a45fb 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesContentProvider.java
@@ -11,28 +11,44 @@
package org.eclipse.cdt.debug.internal.ui.views.executables;
-import com.ibm.icu.text.DateFormat;
import java.util.Date;
+import java.util.List;
import org.eclipse.cdt.debug.core.executables.Executable;
import org.eclipse.cdt.debug.core.executables.ExecutablesManager;
+import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener;
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.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.ui.progress.WorkbenchJob;
+
+import com.ibm.icu.text.DateFormat;
-class ExecutablesContentProvider extends ColumnLabelProvider implements IStructuredContentProvider, ITreeContentProvider {
+class ExecutablesContentProvider extends ColumnLabelProvider implements IStructuredContentProvider, ITreeContentProvider, IExecutablesChangeListener {
- public ExecutablesContentProvider(TreeViewer viewer) {
+ final private TreeViewer viewer;
+
+ public ExecutablesContentProvider(final TreeViewer viewer) {
+ this.viewer = viewer;
+ ExecutablesManager.getExecutablesManager().addExecutablesChangeListener(this);
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.viewers.BaseLabelProvider#dispose()
+ */
+ @Override
public void dispose() {
+ ExecutablesManager.getExecutablesManager().removeExecutablesChangeListener(this);
}
public Object[] getElements(final Object inputElement) {
@@ -100,4 +116,28 @@ class ExecutablesContentProvider extends ColumnLabelProvider implements IStructu
return new Object[] {};
}
+ /* (non-Javadoc)
+ * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesListChanged()
+ */
+ public void executablesListChanged() {
+ new WorkbenchJob("execs list changed") { //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ viewer.refresh(null);
+ if (viewer instanceof BaseViewer) {
+ ((BaseViewer)viewer).packColumns();
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesChanged(java.util.List)
+ */
+ public void executablesChanged(List<Executable> executables) {
+ // Our concern is only if the list of executables changed. The
+ // content provider for the source files viewer will care about
+ // whether the Executables themselves change
+ }
} \ No newline at end of file
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java
index 4a35f5c5b0..83b37d6be7 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesView.java
@@ -58,7 +58,7 @@ import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.eclipse.ui.part.ViewPart;
-import org.eclipse.ui.progress.UIJob;
+import org.eclipse.ui.progress.WorkbenchJob;
/**
* ExecutablesView displays a list of executable files either in the workspace
@@ -176,6 +176,7 @@ public class ExecutablesView extends ViewPart {
class ColumnLabelProvider extends LabelProvider {
+ @Override
public String getText(Object element) {
return (String) element;
}
@@ -193,6 +194,7 @@ public class ExecutablesView extends ViewPart {
*
* @see org.eclipse.jface.action.Action#run()
*/
+ @Override
public void run() {
ListSelectionDialog dialog = new ListSelectionDialog(ExecutablesView.this.getExecutablesViewer().getTree().getShell(), this,
new ColumnContentProvider(), new ColumnLabelProvider(), Messages.ExecutablesView_SelectColumns);
@@ -250,7 +252,6 @@ public class ExecutablesView extends ViewPart {
// Create the two sub viewers.
executablesViewer = new ExecutablesViewer(this, sashForm, SWT.FULL_SELECTION | SWT.BORDER | SWT.MULTI);
focusedViewer = executablesViewer;
- ExecutablesManager.getExecutablesManager().addExecutablesChangeListener(executablesViewer);
sourceFilesViewer = new SourceFilesViewer(this, sashForm, SWT.BORDER | SWT.MULTI);
executablesViewer.getTree().addFocusListener(new FocusListener() {
@@ -416,6 +417,7 @@ public class ExecutablesView extends ViewPart {
private Action createRemoveAction() {
Action action = new Action(Messages.ExecutablesView_Remove) {
+ @Override
public void run() {
ISelection selection = getExecutablesViewer().getSelection();
if (selection instanceof IStructuredSelection)
@@ -432,8 +434,9 @@ public class ExecutablesView extends ViewPart {
if (confirm)
{
- Job removeJob = new UIJob(Messages.ExecutablesView_RemoveExes) {
+ Job removeJob = new WorkbenchJob(Messages.ExecutablesView_RemoveExes) {
+ @Override
public IStatus runInUIThread(IProgressMonitor monitor) {
IStatus result = ExecutablesManager.getExecutablesManager().removeExecutables(selectedExesArray, monitor);
if (result.getSeverity() != IStatus.OK)
@@ -490,6 +493,7 @@ public class ExecutablesView extends ViewPart {
private Action createImportAction() {
Action action = new Action(Messages.ExecutablesView_Import) {
+ @Override
public void run() {
FileDialog dialog = new FileDialog(getViewSite().getShell(), SWT.NONE);
dialog.setText(Messages.ExecutablesView_SelectExeFile);
@@ -515,8 +519,10 @@ public class ExecutablesView extends ViewPart {
private Action createRefreshAction() {
Action action = new Action(Messages.ExecutablesView_Refresh) {
+ @Override
public void run() {
ExecutablesManager.getExecutablesManager().refresh(null);
+ sourceFilesViewer.restartCanceledExecutableParse();
}
};
action.setToolTipText(Messages.ExecutablesView_RefreshList);
@@ -574,7 +580,6 @@ public class ExecutablesView extends ViewPart {
@Override
public void dispose() {
- ExecutablesManager.getExecutablesManager().removeExecutablesChangeListener(executablesViewer);
super.dispose();
}
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java
index bc5f2135c4..cd225886b7 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/ExecutablesViewer.java
@@ -10,19 +10,12 @@
*******************************************************************************/
package org.eclipse.cdt.debug.internal.ui.views.executables;
-import java.util.List;
-
import org.eclipse.cdt.debug.core.executables.Executable;
import org.eclipse.cdt.debug.core.executables.ExecutablesManager;
-import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
-import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerDropAdapter;
@@ -35,12 +28,11 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IWorkbenchActionConstants;
-import org.eclipse.ui.progress.UIJob;
/**
* Displays the list of executables gathered by the ExecutablesManager
*/
-public class ExecutablesViewer extends BaseViewer implements IExecutablesChangeListener {
+public class ExecutablesViewer extends BaseViewer {
private static final String P_COLUMN_ORDER_KEY_EXE = "columnOrderKeyEXE"; //$NON-NLS-1$
private static final String P_SORTED_COLUMN_INDEX_KEY_EXE = "sortedColumnIndexKeyEXE"; //$NON-NLS-1$
@@ -155,6 +147,7 @@ public class ExecutablesViewer extends BaseViewer implements IExecutablesChangeL
protected ViewerComparator getViewerComparator(int sortType) {
if (sortType == ExecutablesView.PROJECT) {
return new ExecutablesViewerComparator(sortType, column_sort_order[ExecutablesView.PROJECT]) {
+ @Override
@SuppressWarnings("unchecked")
public int compare(Viewer viewer, Object e1, Object e2) {
Executable entry1 = (Executable) e1;
@@ -192,46 +185,4 @@ public class ExecutablesViewer extends BaseViewer implements IExecutablesChangeL
// default visible columns
return "1,1,1,0,0,0"; //$NON-NLS-1$
}
-
- public void executablesChanged(final List<Executable> executables) {
- // some executables have been updated. if one of them is currently
- // selected, we need to update the source file list
- UIJob refreshJob = new UIJob(Messages.ExecutablesViewer_RefreshExecutablesView) {
-
- @Override
- public IStatus runInUIThread(IProgressMonitor monitor) {
- // if the user has selected an executable, they expect its
- // list of source files to be refreshed automatically
- if (getSelection() != null &&
- getSelection() instanceof IStructuredSelection) {
- IStructuredSelection selection = (IStructuredSelection)getSelection();
-
- Object firstElement = selection.getFirstElement();
- if (firstElement instanceof Executable) {
- Executable executable = (Executable) firstElement;
- if (executables.contains(executable)) {
- executable.setRefreshSourceFiles(true);
- setSelection(selection);
- }
- }
- }
- return Status.OK_STATUS;
- }
- };
- refreshJob.schedule();
- }
-
- public void executablesListChanged() {
- // Executables list has changed so refresh the view.
- UIJob refreshJob = new UIJob(Messages.ExecutablesViewer_RefreshExecutablesView) {
-
- @Override
- public IStatus runInUIThread(IProgressMonitor monitor) {
- refresh(null);
- packColumns();
- return Status.OK_STATUS;
- }
- };
- refreshJob.schedule();
- }
} \ No newline at end of file
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java
index 1507dfb3d1..be16c63a60 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.java
@@ -53,6 +53,7 @@ public class Messages extends NLS {
public static String SourceFilesContentProvider_NoFilesFound;
public static String SourceFilesContentProvider_ReadingDebugSymbolInformationLabel;
public static String SourceFilesContentProvider_Refreshing;
+ public static String SourceFilesContentProvider_Canceled;
public static String SourceFilesViewer_RefreshSourceFiles;
public static String SourceFilesViewer_Location;
public static String SourceFilesViewer_Modified;
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties
index 4262cb72bc..1cf86d51dd 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/Messages.properties
@@ -48,6 +48,7 @@ ExecutablesViewer_Type=Type
SourceFilesContentProvider_NoFilesFound=No source files found in
SourceFilesContentProvider_ReadingDebugSymbolInformationLabel=Reading Debug Symbol Information:
SourceFilesContentProvider_Refreshing=Refreshing...
+SourceFilesContentProvider_Canceled=Parse canceled. Hit refresh to restart.
SourceFilesViewer_RefreshSourceFiles=Refresh Source Files
SourceFilesViewer_Location=Location
SourceFilesViewer_Modified=Modified
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java
index 80a2ffe999..b515756542 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesContentProvider.java
@@ -18,7 +18,8 @@ import java.util.Map;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.debug.core.executables.Executable;
import org.eclipse.cdt.debug.core.executables.ExecutablesManager;
-import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener;
+import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener2;
+import org.eclipse.cdt.debug.internal.core.Trace;
import org.eclipse.cdt.debug.internal.ui.views.executables.SourceFilesViewer.TranslationUnitInfo;
import org.eclipse.cdt.ui.CElementContentProvider;
import org.eclipse.core.runtime.IPath;
@@ -30,8 +31,9 @@ import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.progress.WorkbenchJob;
-public class SourceFilesContentProvider extends CElementContentProvider implements IExecutablesChangeListener {
+public class SourceFilesContentProvider extends CElementContentProvider implements IExecutablesChangeListener2 {
static class QuickParseJob extends Job {
final Executable executable;
@@ -45,18 +47,65 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen
@Override
protected IStatus run(IProgressMonitor monitor) {
- tus = executable.getSourceFiles(monitor);
- return Status.OK_STATUS;
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Quick parsing of executable for source files has begun (" + this + ')'); //$NON-NLS-1$
+
+ // Ask the Executable for its source files. This could take a while...
+ ITranslationUnit[] mytus = executable.getSourceFiles(monitor);
+
+ IStatus status;
+ if (!monitor.isCanceled()) {
+ tus = mytus;
+ status = Status.OK_STATUS;
+ }
+ else {
+ status = Status.CANCEL_STATUS;
+ }
+
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Quick parsing of executable has finished, status is " + status); //$NON-NLS-1$
+ return status;
}
}
- /** contains running jobs */
+ /**
+ * The collection of running file parsing jobs. Each executable file (not
+ * object) can independently be parsed, and these parses can happen
+ * simultaneously. Normally, each executable file has at most one ongoing
+ * parse. An exception is when a search is canceled. We don't wait for the
+ * search to actually end if a subsequent search comes in shortly after the
+ * first one is canceled. We cancel the first one, remove it from this list,
+ * schedule a new one, then add that to the list. It's safe to assume the
+ * canceled one will complete before the new one.
+ *
+ * <p> This collection must be accessed only from the UI thread
+ */
private Map<IPath, QuickParseJob> pathToJobMap = new HashMap<IPath, SourceFilesContentProvider.QuickParseJob>();
/** those executables for which we asked the question and got a result.
* NOTE: this contains a duplicate of into in Executable, because we can't
* guarantee or check whether Executable still has the info itself. */
- private Map<IPath, ITranslationUnit[]> fetchedExecutables = new HashMap<IPath, ITranslationUnit[]>();
+ private static class TUData{
+ /** Constructor used when search completes successfully */
+ public TUData(ITranslationUnit[] tus, long timestamp) {
+ this.tus = tus;
+ this.timestamp = timestamp;
+ }
+
+ /** Constructor used when search is canceled */
+ public TUData() {
+ this.canceled = true;
+ }
+
+ ITranslationUnit[] tus;
+ /** IResource.getModificationStamp value of when this data was last updated */
+ long timestamp;
+
+ boolean canceled;
+ }
+
+ /**
+ * The cached file info. Key is the path of the executable. This collection must be accessed only on the UI thread.
+ */
+ private Map<IPath, TUData> fetchedExecutables = new HashMap<IPath, TUData>();
private final SourceFilesViewer viewer;
@@ -72,12 +121,14 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen
@Override
public void dispose() {
ExecutablesManager.getExecutablesManager().removeExecutablesChangeListener(this);
- synchronized (fetchedExecutables) {
- fetchedExecutables.clear();
- }
- synchronized (pathToJobMap) {
- pathToJobMap.clear();
- }
+ new WorkbenchJob("") { //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ fetchedExecutables.clear();
+ pathToJobMap.clear();
+ return Status.OK_STATUS;
+ }
+ }.schedule();
super.dispose();
}
@@ -92,63 +143,71 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen
return super.hasChildren(element);
}
+ /* (non-Javadoc)
+ * @see org.eclipse.cdt.internal.ui.BaseCElementContentProvider#getElements(java.lang.Object)
+ */
+ @Override
public Object[] getElements(Object inputElement) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, inputElement);
+
if (inputElement instanceof Executable) {
final Executable executable = (Executable) inputElement;
final IPath exePath = executable.getPath();
// look for a job that is currently fetching this info
QuickParseJob job;
- synchronized (pathToJobMap) {
- job = pathToJobMap.get(exePath);
- }
+ job = pathToJobMap.get(exePath);
if (job != null) {
// job is still running
return new String[] { Messages.SourceFilesContentProvider_Refreshing };
}
+ // create a background job to look for the sources but don't start it yet
+ job = new QuickParseJob(executable);
+ pathToJobMap.put(exePath, job);
- // see if we already checked
- synchronized (fetchedExecutables) {
- if (fetchedExecutables.containsKey(exePath)) {
- return fetchedExecutables.get(exePath);
- }
+ // See if we have the result cached for this executable. If so
+ // return that. It's also possible that the most resent search was
+ // canceled
+ Object[] cachedResult = null;
+ TUData tud = fetchedExecutables.get(exePath);
+ if (tud != null) {
+ if (tud.canceled)
+ cachedResult = new String[]{Messages.SourceFilesContentProvider_Canceled};
+ else
+ cachedResult = tud.tus;
}
-
- // start a background job to look for the sources
- job = new QuickParseJob(executable);
- synchronized (pathToJobMap) {
- pathToJobMap.put(exePath, job);
+ if (cachedResult != null) {
+ pathToJobMap.remove(exePath); // removed the unused search job
+ return cachedResult;
}
- // once the job finishes, update the viewer
+ // Schedule the job; once it finishes, update the viewer
final QuickParseJob theJob = job;
job.addJobChangeListener(new JobChangeAdapter() {
- public void done(IJobChangeEvent event) {
- synchronized (pathToJobMap) {
- pathToJobMap.values().remove(theJob);
- }
- if (event.getResult().isOK()) {
- synchronized (fetchedExecutables) {
- fetchedExecutables.put(exePath, theJob.tus);
- }
- Display.getDefault().asyncExec(new Runnable() {
- public void run() {
- // update the viewer
- if (!viewer.getControl().isDisposed()) {
- viewer.getTree().setLayoutDeferred(true);
- viewer.refresh(executable);
- viewer.packColumns();
- viewer.getTree().setLayoutDeferred(false);
- }
+ @Override
+ public void done(final IJobChangeEvent event) {
+ new WorkbenchJob("refreshing source files viewer"){ //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ if (event.getResult().isOK()) {
+ fetchedExecutables.put(exePath, new TUData(theJob.tus, theJob.executable.getResource().getModificationStamp()));
}
- });
- }
+ else {
+ fetchedExecutables.put(exePath, new TUData());
+ }
+
+ pathToJobMap.values().remove(theJob);
+
+ refreshViewer(executable);
+ return Status.OK_STATUS;
+ }
+ }.schedule();
}
});
-
+
job.schedule();
- // while it's running...
+ // show the user a string that lets him know we're searching
return new String[] { Messages.SourceFilesContentProvider_Refreshing };
}
return new Object[] {};
@@ -159,46 +218,62 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen
* @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesListChanged()
*/
public void executablesListChanged() {
- // Don't clear executables -- closing/opening project doesn't imply
- // the info is different. But cancel all the jobs in case projects
- // were closed. It's non-obvious how to map executables to projects,
- // so just bail and cancel all the current parsing. The viewer
- // will be refreshed and re-request source lists for any executables
- // that are still applicable.
- cancelQuickParseJobs();
+ // we react via IExecutablesChangeListener2 methods
}
- /**
- *
- */
- private void cancelQuickParseJobs() {
- synchronized (pathToJobMap) {
- for (QuickParseJob job : pathToJobMap.values()) {
- job.cancel();
- }
- pathToJobMap.clear();
- }
-
- }
-
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener#executablesChanged(java.util.List)
*/
- public void executablesChanged(List<Executable> executables) {
- for (Executable executable : executables) {
- IPath exePath = executable.getPath();
- synchronized (fetchedExecutables) {
- fetchedExecutables.remove(exePath);
- }
- synchronized (pathToJobMap) {
- QuickParseJob job = pathToJobMap.get(exePath);
- if (job != null) {
- job.cancel();
- pathToJobMap.remove(exePath);
+ public void executablesChanged(final List<Executable> executables) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executables);
+
+ new WorkbenchJob("Refreshing viewer") { //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ for (Executable executable : executables) {
+ IPath exePath = executable.getPath();
+ fetchedExecutables.remove(exePath);
+ QuickParseJob job = pathToJobMap.get(exePath);
+ if (job != null) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Cancelling QuickParseJob: " + job); //$NON-NLS-1$
+ job.cancel();
+ pathToJobMap.remove(exePath);
+ }
+ }
+
+ if (!viewer.getControl().isDisposed()) {
+ // See if our current input is one of the executables that has changed.
+ for (Executable executable : executables) {
+ if (executable.equals(fInput)) {
+ // Executable.equals() is not a simple reference
+ // check. Two Executable objects are equal if they
+ // represent the same file on disk. I.e., our input
+ // object might not be one of the instances on the
+ // changed-list, but for sure the file on disk has
+ // changed. Now, the manager that called this
+ // listener has already told the Executable
+ // instances on the changed list to flush their
+ // source files list. However, if our input is not
+ // exactly one of those references, it means the
+ // manager is no longer managing the Executable
+ // that's our input. In that case, it's up to us to
+ // tell that Executable to flush its source file
+ // cache so that refreshing the viewer will cause a
+ // fresh fetch of the source file information.
+ Executable execInput = (Executable)fInput;
+ if (executable != execInput) {
+ execInput.setRefreshSourceFiles(true);
+ }
+ refreshViewer(execInput);
+ break;
+ }
+ }
}
+ return Status.OK_STATUS;
}
- }
+
+ }.schedule();
}
/* (non-Javadoc)
@@ -208,14 +283,178 @@ public class SourceFilesContentProvider extends CElementContentProvider implemen
public void inputChanged(Viewer viewer, Object oldInput, final Object newInput) {
super.inputChanged(viewer, oldInput, newInput);
- Display.getDefault().asyncExec(new Runnable() {
- public void run() {
+ new WorkbenchJob("Refreshing viewer") { //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
// pack because the quick parse job won't run
if (newInput instanceof Executable
&& fetchedExecutables.containsKey(((Executable) newInput).getPath()))
SourceFilesContentProvider.this.viewer.packColumns();
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener2#executablesAdded(java.util.List)
+ */
+ public void executablesAdded(final List<Executable> executables) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executables);
+
+ // Throw out our cached translation units for the executable *file* but
+ // only if the file hasn't changed. Executable objects come and go
+ // independently of the file on disk.
+ new WorkbenchJob("executables removed") { //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ for (Executable exec : executables) {
+ final IPath exePath = exec.getPath();
+ final long timestamp = exec.getResource().getModificationStamp();
+ TUData tud = fetchedExecutables.get(exePath);
+ if (tud != null && tud.timestamp != timestamp) {
+ fetchedExecutables.remove(exePath);
+ }
+ }
+
+ if (!viewer.getControl().isDisposed()) {
+ // See if current viewer input is one of the executables that
+ // was added. If so, this is likely an exec that was rebuilt
+ // and CDT missed sending a REMOVED model event. There's
+ // some crazy race condition going on, but basically CDT
+ // sends out an event that the binary has changed, then
+ // sends one that says it was added. Anyway, the best thing
+ // for us to do is to cause a refresh of the viewer since
+ // the addition notification probably caused us to cancel
+ // the parse of the exec that was initiated by the change
+ // event and the viewer will be stuck with a "canceled"
+ // message in the viewer table.
+ for (Executable executable : executables) {
+ if (executable.equals(fInput)) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "refreshing viewer; added executable is our current input"); //$NON-NLS-1$
+ refreshViewer((Executable)fInput);
+ break;
+ }
+ }
+ }
+
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener2#executablesRemoved(java.util.List)
+ */
+ public void executablesRemoved(final List<Executable> executables) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().traceEntry(null, executables);
+
+ // The fact that the Executable was removed from the workspace doesn't
+ // mean we need to throw out the source file info we've cached. If a
+ // project is closed then reopened, we are able to reuse the info as
+ // long as the timestamp of the resource hasn't changed. But, there's no
+ // point in continuing any ongoing searches in the executables.
+ new WorkbenchJob("executables removed") { //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ for (Executable exec : executables) {
+ final IPath exePath = exec.getPath();
+ QuickParseJob job = pathToJobMap.get(exePath);
+ if (job != null) {
+ if (Trace.DEBUG_EXECUTABLES) Trace.getTrace().trace(null, "Cancelling QuickParseJob: " + job); //$NON-NLS-1$
+ job.cancel();
+ pathToJobMap.remove(exePath);
+ }
+ }
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+
+ /**
+ * Restarts a parse of the current input (Executable) if and only if its
+ * last search was canceled. The viewer is refresh accordingly.
+ *
+ * <p>
+ * Must be called on the UI thread
+ *
+ */
+ public void restartCanceledExecutableParse() {
+ assert Display.getCurrent() != null;
+
+ Object input = viewer.getInput();
+ if (input instanceof Executable) {
+ final Executable executable = (Executable)input;
+ final IPath exePath = executable.getPath();
+
+ // Ignore restart if there's an ongoing search.
+ QuickParseJob job;
+ job = pathToJobMap.get(exePath);
+ if (job != null) {
+ return;
+ }
+
+ TUData tud = fetchedExecutables.get(exePath);
+
+ // Ignore request if the most recent search wasn't canceled
+ if (tud != null && !tud.canceled) {
+ pathToJobMap.remove(exePath);
+ return;
}
- });
+
+ // Create and schedule a parse job. Once the job finishes, update
+ // the viewer
+ job = new QuickParseJob(executable);
+ pathToJobMap.put(exePath, job);
+ final QuickParseJob theJob = job;
+ job.addJobChangeListener(new JobChangeAdapter() {
+ @Override
+ public void done(final IJobChangeEvent event) {
+
+ new WorkbenchJob("refreshing source files viewer"){ //$NON-NLS-1$
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ // Update the model with the search results
+ if (event.getResult().isOK()) {
+ fetchedExecutables.put(exePath, new TUData(theJob.tus, theJob.executable.getResource().getModificationStamp()));
+ }
+ else {
+ // The search job apparently always completes
+ // successfully or it was canceled (failure was
+ // not a considered outcome). If it was canceled,
+ // well then we're back to where we started
+ fetchedExecutables.put(exePath, new TUData());
+ }
+ pathToJobMap.values().remove(theJob);
+
+ refreshViewer(executable);
+ return Status.OK_STATUS;
+ }
+ }.schedule();
+ }
+ });
+
+ job.schedule();
+
+ // The viewer is currently showing "search canceled". Cause an
+ // immediate refresh so that it shows "refreshing" while the new
+ // search is ongoing
+ refreshViewer(executable);
+ }
+ }
+
+ /**
+ * Utility method to invoke a viewer refresh for the given element
+ * @param input the Executable to show content for
+ *
+ * <p> Must be called on the UI thread
+ */
+ private void refreshViewer(Executable input) {
+ if (!viewer.getControl().isDisposed()) {
+ viewer.getTree().setLayoutDeferred(true);
+ viewer.refresh(input);
+ viewer.packColumns();
+ viewer.getTree().setLayoutDeferred(false);
+ }
}
}
diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java
index f69c3f7c84..640ba1291f 100644
--- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java
+++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/executables/SourceFilesViewer.java
@@ -11,35 +11,28 @@
package org.eclipse.cdt.debug.internal.ui.views.executables;
import java.io.File;
+import java.util.List;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
-import org.eclipse.cdt.debug.core.CDebugCorePlugin;
import org.eclipse.cdt.debug.core.executables.Executable;
+import org.eclipse.cdt.debug.core.executables.ExecutablesManager;
+import org.eclipse.cdt.debug.core.executables.IExecutablesChangeListener;
import org.eclipse.cdt.debug.internal.ui.sourcelookup.CSourceNotFoundEditorInput;
import org.eclipse.cdt.debug.ui.ICDebugUIConstants;
import org.eclipse.cdt.internal.core.util.LRUCache;
import org.eclipse.cdt.internal.ui.util.EditorUtility;
import org.eclipse.cdt.ui.CUIPlugin;
-import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.ILaunchConfiguration;
-import org.eclipse.debug.core.ILaunchConfigurationListener;
-import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
-import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IEditorPart;
@@ -50,7 +43,7 @@ import org.eclipse.ui.PartInitException;
* Displays the list of source files for the executable selected in the
* ExecutablesViewer.
*/
-public class SourceFilesViewer extends BaseViewer implements ISourceLookupParticipant, ILaunchConfigurationListener {
+public class SourceFilesViewer extends BaseViewer {
/** Information from an ITranslationUnit for the various displayed columns */
static class TranslationUnitInfo {
@@ -100,25 +93,21 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic
openSourceFile(event);
}
});
-
- // We implement ISourceLookupParticipant so we can listen for changes to
- // source lookup as this viewer shows both original and remapped
- // locations
- CDebugCorePlugin.getDefault().getCommonSourceLookupDirector().addParticipants(new ISourceLookupParticipant[] { this });
- // We also look for launch configuration changes, since their source
- // locators are involved in source path remapping, too
- DebugPlugin.getDefault().getLaunchManager().addLaunchConfigurationListener(this);
-
- sourceFilesTree.addDisposeListener(new DisposeListener() {
-
- public void widgetDisposed(DisposeEvent e) {
- DebugPlugin.getDefault().getLaunchManager().removeLaunchConfigurationListener(SourceFilesViewer.this);
-
- CDebugCorePlugin.getDefault().getCommonSourceLookupDirector().removeParticipants(
- new ISourceLookupParticipant[] { SourceFilesViewer.this });
+ ExecutablesManager.getExecutablesManager().addExecutablesChangeListener(new IExecutablesChangeListener(){
+ public void executablesListChanged() {
+ // this doesn't directly affect us
}
- });
+
+ public void executablesChanged(List<Executable> executables) {
+ // TODO: be more selective; we don't know what TUs go with which executables yet
+ flushTranslationUnitCache();
+
+ // Note that we don't invoke a viewer refresh. Our content
+ // provider needs to also be listening for this notification.
+ // It's up to him to invoke a refresh on us if the model has
+ // been affected by the Executable change
+ }});
}
private void openSourceFile(OpenEvent event) {
@@ -198,10 +187,12 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic
typeColumn.addSelectionListener(new ColumnSelectionAdapter(ExecutablesView.TYPE));
}
+ @Override
protected ViewerComparator getViewerComparator(int sortType) {
if (sortType == ExecutablesView.ORG_LOCATION) {
return new ExecutablesViewerComparator(sortType, column_sort_order[ExecutablesView.ORG_LOCATION]) {
+ @Override
@SuppressWarnings("unchecked")
public int compare(Viewer viewer, Object e1, Object e2) {
if (e1 instanceof ITranslationUnit && e2 instanceof ITranslationUnit) {
@@ -219,40 +210,6 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic
return new ExecutablesViewerComparator(sortType, column_sort_order[sortType]);
}
- public void dispose() {
- }
-
- public Object[] findSourceElements(Object object) throws CoreException {
- return new Object[0];
- }
-
- public String getSourceName(Object object) throws CoreException {
- return ""; //$NON-NLS-1$
- }
-
- public void init(ISourceLookupDirector director) {
- }
-
- public void sourceContainersChanged(ISourceLookupDirector director) {
- refreshContent();
- }
-
- private void refreshContent() {
- Display.getDefault().asyncExec(new Runnable() {
- public void run() {
- Object input = getInput();
- if (input != null && input instanceof Executable) {
- ((Executable)input).setRemapSourceFiles(true);
-
- // TODO: be more selective; we don't know what TUs go with which executables yet
- flushTranslationUnitCache();
-
- refresh(true);
- }
- }
- });
- }
-
@Override
protected String getColumnOrderKey() {
return P_COLUMN_ORDER_KEY_SF;
@@ -279,34 +236,6 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic
return "1,1,0,0,0,0"; //$NON-NLS-1$
}
- /* (non-Javadoc)
- * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationAdded(org.eclipse.debug.core.ILaunchConfiguration)
- */
- public void launchConfigurationAdded(ILaunchConfiguration configuration) {
- if (!configuration.isWorkingCopy()) {
- refreshContent();
- }
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationChanged(org.eclipse.debug.core.ILaunchConfiguration)
- */
- public void launchConfigurationChanged(ILaunchConfiguration configuration) {
- if (!configuration.isWorkingCopy()) {
- refreshContent();
- }
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationRemoved(org.eclipse.debug.core.ILaunchConfiguration)
- */
- public void launchConfigurationRemoved(ILaunchConfiguration configuration) {
- if (!configuration.isWorkingCopy()) {
- refreshContent();
- }
- }
-
-
static TranslationUnitInfo fetchTranslationUnitInfo(Executable executable, Object element) {
if (!(element instanceof ITranslationUnit)) {
return null;
@@ -359,4 +288,17 @@ public class SourceFilesViewer extends BaseViewer implements ISourceLookupPartic
}
+ /**
+ * The view's refresh action calls this to restart an executable parse for
+ * the current input if the most recent search (for that element) was
+ * canceled. If it wasn't canceled, this is a no-op.
+ */
+ public void restartCanceledExecutableParse() {
+ SourceFilesContentProvider provider = (SourceFilesContentProvider)getContentProvider();
+ if (provider != null) {
+ provider.restartCanceledExecutableParse();
+ }
+
+ }
+
} \ No newline at end of file

Back to the top