/******************************************************************************* * Copyright (c) 2010 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.internal.core.srcfinder; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.cdt.core.ISourceFinder; import org.eclipse.cdt.core.model.IBinary; import org.eclipse.cdt.debug.core.CDebugCorePlugin; import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants; import org.eclipse.cdt.debug.core.sourcelookup.ProgramRelativePathSourceContainer; import org.eclipse.cdt.debug.internal.core.sourcelookup.CSourceLookupDirector; import org.eclipse.cdt.internal.core.model.ExternalTranslationUnit; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationListener; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.ILaunchesListener; import org.eclipse.debug.core.model.IPersistableSourceLocator; import org.eclipse.debug.core.model.ISourceLocator; import org.eclipse.debug.core.sourcelookup.IPersistableSourceLocator2; import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage; public class CSourceFinder implements ISourceFinder, ILaunchConfigurationListener, ILaunchesListener { /** * The binary we are searching files for. We need this reference to find a * matching ILaunch or ILaunchConfiguration if the caller needs to search * for files when it doesn't have a debug context. */ private IBinary fBinary; /** * The locator tied to an ILaunch or an ILaunchConfiguration that is * associated with the binary. Used for searching when the caller has no * debug context. See {@link ISourceFinder#toLocalPath(String)} for * performance considerations that dictate how the locator is chosen. Access * this only from synchronized blocks as the field is subject to be changed * by listener invocations. */ private ISourceLookupDirector fLaunchLocator; /** * A launch configuration doesn't have a source locator instance tied to it. * Instead, one is created on the fly as needed from attributes in the * 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<String, ISourceLocator> fConfigLocators = Collections.synchronizedMap(new HashMap<String, ISourceLocator>()); /** * We use this when we don't have an ILaunch or ILaunchConfiguration * locator. A program relative container instance is automatically added to * every CDT launch configuration. So when we lack a configuration context, * we rely on this container to help us resolve relative paths. */ private ProgramRelativePathSourceContainer fRelativePathContainer; /** * Constructor. * * @param binary * the executable whose source files we will be asked to find * locally */ public CSourceFinder(IBinary binary) { assert(binary != null); fBinary = binary; fRelativePathContainer = new ProgramRelativePathSourceContainer(binary); ILaunchManager lmgr = DebugPlugin.getDefault().getLaunchManager(); lmgr.addLaunchConfigurationListener(this); lmgr.addLaunchListener(this); } /* (non-Javadoc) * @see org.eclipse.cdt.core.ISourceFinder#toLocalPath(java.lang.String) */ synchronized public String toLocalPath(String compilationPath) { try { Object foundElement = null; // Find a suitable launch/config locator if we haven't found one yet if (fLaunchLocator == null) { ILaunchManager lmgr = DebugPlugin.getDefault().getLaunchManager(); // See if there are any active debug sessions (running, or // terminated but still in the Debug view) that are targeting // our executable. If there are then use the first one to // provide a locator ILaunch[] launches = lmgr.getLaunches(); for (ILaunch launch : launches) { ILaunchConfiguration config = launch.getLaunchConfiguration(); if (config != null && isMatch(config)) { ISourceLocator launchLocator = launch.getSourceLocator(); // in practice, a launch locator is always an ISourceLookupDirector if (launchLocator instanceof ISourceLookupDirector) { fLaunchLocator = (ISourceLookupDirector)launchLocator; break; } } } // If there were no matching launches or none of them // provided a locator, search the launch configurations 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(configName); if (configLocator == null) { configLocator = getLocator(config); // heavy operation fConfigLocators.put(configName, configLocator); // cache to avoid next time } // In practice, a config's locator is always an ISourceLookupDirector if (configLocator instanceof ISourceLookupDirector) { fLaunchLocator = (ISourceLookupDirector)configLocator; break; } } } } } // Search for the file using the launch/config locator if (fLaunchLocator != null) { foundElement = fLaunchLocator.getSourceElement(compilationPath); } else { // If there isn't a launch/config locator, we need to explicitly // try to resolve relative paths...relative to the binary // location. Object[] elements = fRelativePathContainer.findSourceElements(compilationPath); if (elements.length > 0) { assert elements.length == 1; // relative path container should return at most one element foundElement = elements[0]; } } // If we didn't search using the locator of an ILaunch or // ILaunchConfiguration, look in the global (common) locator. The // common locator is automatically included in every // launch/configuration locator, which is why we skip looking // through the common one again here. if ((fLaunchLocator == null) && (foundElement == null)) { CSourceLookupDirector locator = CDebugCorePlugin.getDefault().getCommonSourceLookupDirector(); foundElement = locator.getSourceElement(compilationPath); } return foundElementToPath(foundElement); } catch (CoreException exc) { CDebugCorePlugin.log(exc); } return null; } /* (non-Javadoc) * @see org.eclipse.cdt.core.ISourceFinder#toLocalPath(org.eclipse.core.runtime.IAdaptable, java.lang.String) */ public String toLocalPath(IAdaptable _launch, String compilationPath) { Object foundElement = null; ILaunch launch = (ILaunch)_launch.getAdapter(ILaunch.class); if (launch != null) { ISourceLocator locator = launch.getSourceLocator(); // in practice, a launch locator is always an ISourceLookupDirector if (locator instanceof ISourceLookupDirector) { foundElement = ((ISourceLookupDirector)locator).getSourceElement(compilationPath); } } // If not found, look in the global (common) locator if (foundElement == null) { CSourceLookupDirector locator = CDebugCorePlugin.getDefault().getCommonSourceLookupDirector(); foundElement = locator.getSourceElement(compilationPath); } return foundElementToPath(foundElement); } /** * Utility method to convert the element found by the source locators to a * canonical file path * * @param foundElement * the element found by the source locator, or null if not found * @return the canonical file path of the element */ private static String foundElementToPath(Object foundElement) { if (foundElement != null) { try { if (foundElement instanceof IFile) { IPath path = ((IFile)foundElement).getLocation(); if (path != null) { File file = path.toFile(); if (file != null) { return file.getCanonicalPath(); } } } else if (foundElement instanceof LocalFileStorage) { File file = ((LocalFileStorage)foundElement).getFile(); if (file != null) { return file.getCanonicalPath(); } } else if (foundElement instanceof ExternalTranslationUnit) { URI uri = ((ExternalTranslationUnit)foundElement).getLocationURI(); if (uri != null) { IPath path = URIUtil.toPath(uri); if (path != null) { File file = path.toFile(); if (file != null) { return file.getCanonicalPath(); } } } } } catch (IOException e) { CDebugCorePlugin.log(e); } } return null; } /** * Utility method to determine if the given launch configuration targets the Binary we are associated with * @param config * @return true if the launch config targets our binary, false otherwise */ private boolean isMatch(ILaunchConfiguration config) { IResource resource = (IResource)fBinary.getAdapter(IResource.class); if (resource != null) { String binaryPath = resource.getFullPath().toString(); try { String projectNameConfig = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); //$NON-NLS-1$ String programNameConfig = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, ""); //$NON-NLS-1$ IProject project = resource.getProject(); if (project != null && project.getName().equals(projectNameConfig)) { Path path = new Path(programNameConfig); if (!path.isEmpty()) { IFile file = project.getFile(path); if (file != null) { String fullPath = file.getFullPath().toString(); return fullPath.equals(binaryPath); } } } } catch (CoreException e) { // Problem getting attribute from launch config? Not expecting that. CDebugCorePlugin.log(e); } } return false; } /** * Utility method to instantiate a source locator for a launch * configuration. A launch configuration doesn't have a source locator * instance tied to it. Transient instances are created as needed. from * attributes in the launch config. This is a heavy operation. * * @param config * the launch configuration to create the locator for * @return the source locator * @throws CoreException */ static private ISourceLocator getLocator(ILaunchConfiguration config) throws CoreException { String type = config.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_ID, (String)null); if (type == null) { type = config.getType().getSourceLocatorId(); } if (type != null) { IPersistableSourceLocator locator = DebugPlugin.getDefault().getLaunchManager().newSourceLocator(type); String memento = config.getAttribute(ILaunchConfiguration.ATTR_SOURCE_LOCATOR_MEMENTO, (String)null); if (memento == null) { locator.initializeDefaults(config); } else { if(locator instanceof IPersistableSourceLocator2) ((IPersistableSourceLocator2)locator).initializeFromMemento(memento, config); else locator.initializeFromMemento(memento); } return locator; } return null; } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationAdded(org.eclipse.debug.core.ILaunchConfiguration) */ public void launchConfigurationAdded(ILaunchConfiguration config) { // Don't care. } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationChanged(org.eclipse.debug.core.ILaunchConfiguration) */ 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.getName()); if ((fLaunchLocator != null) && (fLaunchLocator.getLaunchConfiguration().getName() == config.getName())) { fLaunchLocator = null; } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationRemoved(org.eclipse.debug.core.ILaunchConfiguration) */ 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; } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener#launchesRemoved(org.eclipse.debug.core.ILaunch[]) */ synchronized public void launchesRemoved(ILaunch[] launches) { for (ILaunch launch : launches) { if (launch.getSourceLocator() == fLaunchLocator) { fLaunchLocator = null; return; } } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener#launchesAdded(org.eclipse.debug.core.ILaunch[]) */ 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 // new launch's locator should take precedence for (ILaunch launch : launches) { ILaunchConfiguration config = launch.getLaunchConfiguration(); if (config != null && isMatch(config)) { synchronized(this) { fLaunchLocator = null; } } } } /* (non-Javadoc) * @see org.eclipse.debug.core.ILaunchesListener#launchesChanged(org.eclipse.debug.core.ILaunch[]) */ public void launchesChanged(ILaunch[] launches) { // don't care. I don't think setting a new locator in a launch would result in us getting notified } /* (non-Javadoc) * @see org.eclipse.cdt.core.ISourceFinder#dispose() */ public void dispose() { ILaunchManager lmgr = DebugPlugin.getDefault().getLaunchManager(); lmgr.removeLaunchConfigurationListener(this); lmgr.removeLaunchListener(this); } }