| /******************************************************************************* |
| * Copyright (c) 2011-2016 Igor Fedorenko |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Igor Fedorenko - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.launching.sourcelookup.advanced; |
| |
| import static org.eclipse.jdt.internal.launching.sourcelookup.advanced.AdvancedSourceLookupSupport.getContextMonitor; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtensionRegistry; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.model.DebugElement; |
| import org.eclipse.debug.core.model.IDebugElement; |
| import org.eclipse.debug.core.model.ISourceLocator; |
| import org.eclipse.debug.core.sourcelookup.ISourceContainer; |
| import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; |
| import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant; |
| import org.eclipse.jdt.internal.launching.sourcelookup.advanced.AdvancedSourceLookupSupport; |
| import org.eclipse.jdt.internal.launching.sourcelookup.advanced.CompositeSourceContainer; |
| import org.eclipse.jdt.internal.launching.sourcelookup.advanced.IJDIHelpers; |
| import org.eclipse.jdt.internal.launching.sourcelookup.advanced.WorkspaceProjectSourceContainers; |
| |
| /** |
| * @since 3.10 |
| * @provisional This is part of work in progress and can be changed, moved or removed without notice |
| */ |
| public class AdvancedSourceLookupParticipant implements ISourceLookupParticipant { |
| |
| private final IJDIHelpers jdi; |
| |
| private ISourceLookupDirector director; |
| |
| private final Map<File, ISourceContainer> containers = new HashMap<>(); |
| |
| public AdvancedSourceLookupParticipant() { |
| this(IJDIHelpers.INSTANCE); |
| } |
| |
| /** |
| * @noreference this constructor is visible for test purposes only |
| */ |
| public AdvancedSourceLookupParticipant(IJDIHelpers jdi) { |
| this.jdi = jdi; |
| } |
| |
| @Override |
| public void init(ISourceLookupDirector director) { |
| this.director = director; |
| } |
| |
| @Override |
| public Object[] findSourceElements(Object element) throws CoreException { |
| ISourceContainer container = getSourceContainer(element, false /* don't refresh cache */, null /* async */ ); |
| |
| if (container == null) { |
| return null; |
| } |
| |
| String sourcePath = jdi.getSourcePath(element); |
| if (sourcePath == null) { |
| // can't really happen |
| return null; |
| } |
| |
| return container.findSourceElements(sourcePath); |
| } |
| |
| public ISourceContainer getSourceContainer(Object element, boolean refresh, IProgressMonitor monitor) throws CoreException { |
| File location = jdi.getClassesLocation(element); |
| |
| if (location == null) { |
| return null; |
| } |
| |
| synchronized (containers) { |
| if (!refresh && containers.containsKey(location)) { |
| return containers.get(location); |
| } |
| } |
| |
| monitor = getContextMonitor(monitor); |
| |
| WorkspaceProjectSourceContainers projectLocator = AdvancedSourceLookupSupport.getWorkspaceJavaProjects(monitor); |
| if (monitor == null && projectLocator == null) { |
| // reschedule to initialize and resolve sources in background |
| AdvancedSourceLookupSupport.schedule((m) -> getSourceContainer(element, refresh, m)); |
| return null; |
| } |
| |
| if (projectLocator == null) { |
| // we get here when projectLocator initialization has been cancelled by the user |
| return null; |
| } |
| |
| // |
| // lookup strategies that provide java project context necessary for debug expression evaluation |
| // |
| |
| // workspace project identified by their runtime classes location is the preferred sources container |
| ISourceContainer projectContainer = projectLocator.createProjectContainer(location); |
| if (projectContainer != null) { |
| return cacheContainer(element, location, projectContainer); |
| } |
| |
| // dependency of one of workspace projects on the call stack also provides java project context |
| for (File frameLocation : jdi.getStackFramesClassesLocations(element)) { |
| ISourceContainer entryContainer = projectLocator.createClasspathEntryContainer(frameLocation, location); |
| if (entryContainer != null) { |
| return cacheContainer(element, location, entryContainer); |
| } |
| } |
| |
| if (monitor == null) { |
| // reschedule to resolve sources in background |
| AdvancedSourceLookupSupport.schedule((m) -> getSourceContainer(element, refresh, m)); |
| return null; |
| } |
| |
| // |
| // strategies that allow source code lookup but do not provide java project context |
| // |
| |
| // checksum-based lookup in various sources (central, nexus, pde target platform, p2 repositories) |
| for (ISourceContainerResolver repository : getSourceContainerResolvers()) { |
| Collection<ISourceContainer> members = repository.resolveSourceContainers(location, monitor); |
| if (members != null && !members.isEmpty()) { |
| return cacheContainer(element, location, CompositeSourceContainer.compose(members)); |
| } |
| } |
| |
| return null; |
| } |
| |
| private ISourceContainer cacheContainer(Object element, File location, ISourceContainer container) { |
| ISourceContainer oldContainer; |
| synchronized (containers) { |
| oldContainer = containers.put(location, container); |
| if (oldContainer != null) { |
| oldContainer.dispose(); |
| } |
| } |
| if (oldContainer != null || container != null) { |
| updateDebugElement(element); |
| } |
| return container; |
| } |
| |
| protected Collection<ISourceContainerResolver> getSourceContainerResolvers() { |
| List<ISourceContainerResolver> result = new ArrayList<>(); |
| |
| IExtensionRegistry registry = Platform.getExtensionRegistry(); |
| |
| IConfigurationElement[] elements = registry.getConfigurationElementsFor(AdvancedSourceLookupSupport.ID_sourceContainerResolvers); |
| |
| for (IConfigurationElement element : elements) { |
| if ("resolver".equals(element.getName())) { //$NON-NLS-1$ |
| try { |
| result.add((ISourceContainerResolver) element.createExecutableExtension("class")); //$NON-NLS-1$ |
| } |
| catch (CoreException e) { |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public String getSourceName(Object object) throws CoreException { |
| return null; // default name->source mapping |
| } |
| |
| @Override |
| public void dispose() { |
| disposeContainers(); |
| } |
| |
| @Override |
| public void sourceContainersChanged(ISourceLookupDirector director) { |
| disposeContainers(); |
| } |
| |
| protected void disposeContainers() { |
| synchronized (containers) { |
| for (ISourceContainer container : containers.values()) { |
| if (container != null) // possible for non-maven jars |
| { |
| container.dispose(); |
| } |
| } |
| containers.clear(); |
| } |
| } |
| |
| private void updateDebugElement(Object element) { |
| director.clearSourceElements(element); |
| if (element instanceof DebugElement) { |
| // this is apparently needed to flush StackFrameSourceDisplayAdapter cache |
| ((DebugElement) element).fireChangeEvent(DebugEvent.CONTENT); |
| } |
| } |
| |
| public static AdvancedSourceLookupParticipant getSourceLookup(Object element) { |
| ISourceLocator sourceLocator = null; |
| if (element instanceof IDebugElement) { |
| sourceLocator = ((IDebugElement) element).getLaunch().getSourceLocator(); |
| } |
| |
| AdvancedSourceLookupParticipant sourceLookup = null; |
| if (sourceLocator instanceof ISourceLookupDirector) { |
| for (ISourceLookupParticipant participant : ((ISourceLookupDirector) sourceLocator).getParticipants()) { |
| if (participant instanceof AdvancedSourceLookupParticipant) { |
| sourceLookup = (AdvancedSourceLookupParticipant) participant; |
| break; |
| } |
| } |
| } |
| return sourceLookup; |
| } |
| |
| } |