diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java index 760241517..d885967cf 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Constants.java @@ -14,4 +14,5 @@ public final class Constants { public static final String PROJECTNAME = "projectName"; public static final String DEBUGGEE_ENCODING = "debuggeeEncoding"; + public static final String MAINCLASS = "mainClass"; } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java index a4c1d27c7..92352ed31 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchRequestHandler.java @@ -108,12 +108,16 @@ public CompletableFuture handle(Command command, Arguments arguments, return launch(launchArguments, response, context).thenCompose(res -> { if (res.success) { - ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class); + Map options = new HashMap<>(); options.put(Constants.DEBUGGEE_ENCODING, context.getDebuggeeEncoding()); if (launchArguments.projectName != null) { options.put(Constants.PROJECTNAME, launchArguments.projectName); } + if (launchArguments.mainClass != null) { + options.put(Constants.MAINCLASS, launchArguments.mainClass); + } + ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class); sourceProvider.initialize(context, options); IEvaluationProvider evaluationProvider = context.getProvider(IEvaluationProvider.class); evaluationProvider.initialize(context, options); @@ -336,7 +340,7 @@ private String[] constructLaunchCommands(LaunchArguments launchArguments, boolea } /** - * Parses the given command line into separate arguments that can be passed + * Parses the given command line i`nto separate arguments that can be passed * to Runtime.getRuntime().exec(cmdArray). * * @param args command line as a single string. diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java index e1879fd6d..9adf6b457 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/eval/JdtEvaluationProvider.java @@ -11,13 +11,20 @@ package com.microsoft.java.debug.plugin.internal.eval; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.ILaunch; @@ -29,6 +36,7 @@ import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector; import org.eclipse.debug.core.sourcelookup.containers.ProjectSourceContainer; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.debug.eval.ICompiledExpression; import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget; import org.eclipse.jdt.internal.debug.core.model.JDIStackFrame; @@ -41,6 +49,7 @@ import com.microsoft.java.debug.core.adapter.IDebugAdapterContext; import com.microsoft.java.debug.core.adapter.IEvaluationProvider; import com.microsoft.java.debug.plugin.internal.JdtUtils; +import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; import com.sun.jdi.Value; @@ -55,6 +64,10 @@ public class JdtEvaluationProvider implements IEvaluationProvider { private IDebugAdapterContext context; + private List possibleProjects; + + private Set visitedClassNames = new HashSet<>(); + public JdtEvaluationProvider() { } @@ -70,15 +83,11 @@ public void initialize(IDebugAdapterContext context, Map props) @Override public CompletableFuture evaluate(String expression, ThreadReference thread, int depth) { CompletableFuture completableFuture = new CompletableFuture<>(); - String projectName = (String) options.get(Constants.PROJECTNAME); + if (debugTarget == null) { + String projectName = (String) options.get(Constants.PROJECTNAME); if (project == null) { - if (StringUtils.isBlank(projectName)) { - logger.severe("Cannot evaluate when project is not specified."); - completableFuture.completeExceptionally(new IllegalStateException("Please specify projectName in launch.json.")); - return completableFuture; - } - project = JdtUtils.getJavaProject(projectName); + initializeJavaProject(projectName, thread, depth); } if (project == null) { @@ -98,8 +107,8 @@ protected synchronized void initialize() { } }; } - JDIThread jdiThread = getMockJDIThread(thread); + JDIStackFrame stackframe = createStackFrame(jdiThread, depth); if (stackframe == null) { logger.severe("Cannot evaluate because the stackframe is not available."); @@ -107,6 +116,7 @@ protected synchronized void initialize() { new IllegalStateException("Cannot evaluate because the stackframe is not available.")); return completableFuture; } + try { ASTEvaluationEngine engine = new ASTEvaluationEngine(project, debugTarget); ICompiledExpression ie = engine.getCompiledExpression(expression, stackframe); @@ -132,6 +142,91 @@ protected synchronized void initialize() { return completableFuture; } + /** + * Prepare a list of possible java projects in workspace which contains the main class. + * @param projectName the possible project name specified by launch.json + * @param mainclass the main class specified by launch.json for finding possible projects + */ + private void initializePossibleProjects(String projectName, String mainclass) { + + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + List projects = Arrays.stream(root.getProjects()).map(JdtUtils::getJavaProject).filter(p -> { + try { + return p != null && p.hasBuildState(); + } catch (Exception e) { + // ignore + } + return false; + }).collect(Collectors.toList()); + + + if (projects.size() > 1 && StringUtils.isNotBlank(mainclass)) { + projects = Arrays.stream(root.getProjects()).map(JdtUtils::getJavaProject).filter(p -> { + try { + return p.findType(mainclass) != null; + } catch (JavaModelException e) { + // ignore + } + return false; + }).collect(Collectors.toList()); + visitedClassNames.add(mainclass); + } + + if (projects.size() == 1) { + project = projects.get(0); + } + + possibleProjects = projects; + } + + private void initializeJavaProject(String projectName, ThreadReference thread, int depth) { + if (possibleProjects == null) { + initializePossibleProjects((String) options.get(Constants.PROJECTNAME), (String) options.get(Constants.MAINCLASS)); + if (project != null) { + return; + } + } + + if (possibleProjects.size() == 0) { + logger.severe("No project is available for evaluation."); + throw new IllegalStateException("No project is available for evaluation."); + } + + try { + StackFrame sf = thread.frame(depth); + String typeName = sf.location().method().declaringType().name(); + // check whether the project is the only java project in workspace + List validProjects = visitedClassNames.contains(typeName) ? possibleProjects + : possibleProjects.stream().filter(p -> { + try { + return !visitedClassNames.contains(typeName) && p.findType(typeName) != null; + } catch (Exception e) { + // ignore + } + return false; + }).collect(Collectors.toList()); + visitedClassNames.add(typeName); + if (validProjects.size() == 1) { + project = validProjects.get(0); + } else if (validProjects.size() == 0) { + logger.severe("No project is available for evaluation."); + throw new IllegalStateException("No project is available for evaluation, ."); + } else { + // narrow down projects + possibleProjects = validProjects; + logger.severe("Multiple projects are valid for evaluation."); + throw new IllegalStateException("Multiple projects are found, please specify projectName in launch.json."); + } + + } catch (Exception ex) { + // ignore + } + + logger.severe("Cannot evaluate when project is not specified."); + throw new IllegalStateException("Please specify projectName in launch.json."); + } + + private JDIStackFrame createStackFrame(JDIThread thread, int depth) { try { IStackFrame[] jdiStackFrames = thread.getStackFrames();