1111
1212package com .microsoft .java .debug .plugin .internal .eval ;
1313
14+ import java .util .Arrays ;
1415import java .util .HashMap ;
16+ import java .util .HashSet ;
17+ import java .util .List ;
1518import java .util .Map ;
19+ import java .util .Set ;
1620import java .util .concurrent .CompletableFuture ;
1721import java .util .logging .Logger ;
22+ import java .util .stream .Collectors ;
1823
1924import org .apache .commons .lang3 .StringUtils ;
2025import org .apache .commons .lang3 .reflect .FieldUtils ;
26+ import org .eclipse .core .resources .IWorkspaceRoot ;
27+ import org .eclipse .core .resources .ResourcesPlugin ;
2128import org .eclipse .core .runtime .CoreException ;
2229import org .eclipse .debug .core .DebugException ;
2330import org .eclipse .debug .core .ILaunch ;
2936import org .eclipse .debug .core .sourcelookup .AbstractSourceLookupDirector ;
3037import org .eclipse .debug .core .sourcelookup .containers .ProjectSourceContainer ;
3138import org .eclipse .jdt .core .IJavaProject ;
39+ import org .eclipse .jdt .core .JavaModelException ;
3240import org .eclipse .jdt .debug .core .IJavaStackFrame ;
3341import org .eclipse .jdt .debug .eval .ICompiledExpression ;
3442import org .eclipse .jdt .internal .debug .core .model .JDIDebugTarget ;
4351import com .microsoft .java .debug .core .adapter .IDebugAdapterContext ;
4452import com .microsoft .java .debug .core .adapter .IEvaluationProvider ;
4553import com .microsoft .java .debug .plugin .internal .JdtUtils ;
54+ import com .sun .jdi .StackFrame ;
4655import com .sun .jdi .ThreadReference ;
4756import com .sun .jdi .Value ;
4857import com .sun .jdi .VirtualMachine ;
@@ -56,6 +65,10 @@ public class JdtEvaluationProvider implements IEvaluationProvider {
5665 private HashMap <String , Object > options = new HashMap <>();
5766 private IDebugAdapterContext context ;
5867
68+ private List <IJavaProject > projectCandidates ;
69+
70+ private Set <String > visitedClassNames = new HashSet <>();
71+
5972 public JdtEvaluationProvider () {
6073 }
6174
@@ -80,7 +93,7 @@ public CompletableFuture<Value> evaluateForBreakpoint(IBreakpoint breakpoint, Th
8093
8194 CompletableFuture <Value > completableFuture = new CompletableFuture <>();
8295 try {
83- ensureDebugTarget (thread .virtualMachine ());
96+ ensureDebugTarget (thread .virtualMachine (), thread , 0 );
8497 JDIThread jdiThread = getMockJDIThread (thread );
8598 JDIStackFrame stackframe = (JDIStackFrame ) jdiThread .getTopStackFrame ();
8699
@@ -101,7 +114,7 @@ public CompletableFuture<Value> evaluateForBreakpoint(IBreakpoint breakpoint, Th
101114 public CompletableFuture <Value > evaluate (String expression , ThreadReference thread , int depth ) {
102115 CompletableFuture <Value > completableFuture = new CompletableFuture <>();
103116 try {
104- ensureDebugTarget (thread .virtualMachine ());
117+ ensureDebugTarget (thread .virtualMachine (), thread , depth );
105118 JDIThread jdiThread = getMockJDIThread (thread );
106119 JDIStackFrame stackframe = createStackFrame (jdiThread , depth );
107120 if (stackframe == null ) {
@@ -119,6 +132,91 @@ public CompletableFuture<Value> evaluate(String expression, ThreadReference thre
119132 }
120133 }
121134
135+ /**
136+ * Prepare a list of java project candidates in workspace which contains the main class.
137+ *
138+ * @param mainclass the main class specified by launch.json for finding project candidates
139+ */
140+ private void initializeProjectCandidates (String mainclass ) {
141+ IWorkspaceRoot root = ResourcesPlugin .getWorkspace ().getRoot ();
142+ List <IJavaProject > projects = Arrays .stream (root .getProjects ()).map (JdtUtils ::getJavaProject ).filter (p -> {
143+ try {
144+ return p != null && p .hasBuildState ();
145+ } catch (Exception e ) {
146+ // ignore
147+ }
148+ return false ;
149+ }).collect (Collectors .toList ());
150+
151+
152+ if (projects .size () > 1 && StringUtils .isNotBlank (mainclass )) {
153+ projects = Arrays .stream (root .getProjects ()).map (JdtUtils ::getJavaProject ).filter (p -> {
154+ try {
155+ return p .findType (mainclass ) != null ;
156+ } catch (JavaModelException e ) {
157+ // ignore
158+ }
159+ return false ;
160+ }).collect (Collectors .toList ());
161+ visitedClassNames .add (mainclass );
162+ }
163+
164+ if (projects .size () == 1 ) {
165+ project = projects .get (0 );
166+ }
167+
168+ projectCandidates = projects ;
169+ }
170+
171+ private void findJavaProjectByStackFrame (ThreadReference thread , int depth ) {
172+ if (projectCandidates == null ) {
173+ // initial candidate projects by main class (projects contains this main class)
174+ initializeProjectCandidates ((String ) options .get (Constants .MAIN_CLASS ));
175+ if (project != null ) {
176+ return ;
177+ }
178+ }
179+
180+ if (projectCandidates .size () == 0 ) {
181+ logger .severe ("No project is available for evaluation." );
182+ throw new IllegalStateException ("No project is available for evaluation." );
183+ }
184+
185+ try {
186+ StackFrame sf = thread .frame (depth );
187+ String typeName = sf .location ().method ().declaringType ().name ();
188+ // narrow down candidate projects by current class
189+ List <IJavaProject > validProjects = visitedClassNames .contains (typeName ) ? projectCandidates
190+ : projectCandidates .stream ().filter (p -> {
191+ try {
192+ return !visitedClassNames .contains (typeName ) && p .findType (typeName ) != null ;
193+ } catch (Exception e ) {
194+ // ignore
195+ }
196+ return false ;
197+ }).collect (Collectors .toList ());
198+ visitedClassNames .add (typeName );
199+ if (validProjects .size () == 1 ) {
200+ project = validProjects .get (0 );
201+ } else if (validProjects .size () == 0 ) {
202+ logger .severe ("No project is available for evaluation." );
203+ throw new IllegalStateException ("No project is available for evaluation, ." );
204+ } else {
205+ // narrow down projects
206+ projectCandidates = validProjects ;
207+ logger .severe ("Multiple projects are valid for evaluation." );
208+ throw new IllegalStateException ("Multiple projects are found, please specify projectName in launch.json." );
209+ }
210+
211+ } catch (Exception ex ) {
212+ // ignore
213+ }
214+
215+ logger .severe ("Cannot evaluate when the project is not specified." );
216+ throw new IllegalStateException ("Please specify projectName in launch.json." );
217+ }
218+
219+
122220 private JDIStackFrame createStackFrame (JDIThread thread , int depth ) {
123221 try {
124222 IStackFrame [] jdiStackFrames = thread .getStackFrames ();
@@ -188,19 +286,19 @@ public void clearState(ThreadReference thread) {
188286 }
189287 }
190288
191- private void ensureDebugTarget (VirtualMachine vm ) {
289+ private void ensureDebugTarget (VirtualMachine vm , ThreadReference thread , int depth ) {
192290 if (debugTarget == null ) {
193- String projectName = (String ) options .get (Constants .PROJECTNAME );
194291 if (project == null ) {
292+ String projectName = (String ) options .get (Constants .PROJECT_NAME );
195293 if (StringUtils .isBlank (projectName )) {
196- logger .severe ("Cannot evaluate when project is not specified." );
197- throw new IllegalStateException ("Please specify projectName in launch.json." );
198- }
199- IJavaProject javaProject = JdtUtils .getJavaProject (projectName );
200- if (javaProject == null ) {
201- throw new IllegalStateException (String .format ("Project %s cannot be found." , projectName ));
294+ findJavaProjectByStackFrame (thread , depth );
295+ } else {
296+ IJavaProject javaProject = JdtUtils .getJavaProject (projectName );
297+ if (javaProject == null ) {
298+ throw new IllegalStateException (String .format ("Project %s cannot be found." , projectName ));
299+ }
300+ project = javaProject ;
202301 }
203- project = javaProject ;
204302 }
205303
206304 if (launch == null ) {
0 commit comments