11package org .javacs ;
22
3+ import com .sun .jdi .ReferenceType ;
4+ import com .sun .jdi .VirtualMachine ;
35import java .io .IOException ;
6+ import java .lang .reflect .Field ;
7+ import java .lang .reflect .InvocationTargetException ;
8+ import java .lang .reflect .Method ;
9+ import java .lang .reflect .Proxy ;
410import java .nio .file .Path ;
511import java .nio .file .Paths ;
612import java .util .List ;
@@ -81,6 +87,98 @@ private void setBreakpoint(String className, int line) {
8187 server .setBreakpoints (set );
8288 }
8389
90+ private static void setVm (JavaDebugServer server , VirtualMachine vm ) throws ReflectiveOperationException {
91+ Field field = JavaDebugServer .class .getDeclaredField ("vm" );
92+ field .setAccessible (true );
93+ field .set (server , vm );
94+ }
95+
96+ @ SuppressWarnings ("unchecked" )
97+ private static List <Breakpoint > pendingBreakpoints (JavaDebugServer server ) throws ReflectiveOperationException {
98+ Field field = JavaDebugServer .class .getDeclaredField ("pendingBreakpoints" );
99+ field .setAccessible (true );
100+ return (List <Breakpoint >) field .get (server );
101+ }
102+
103+ private static Method privateMethod (String name , Class <?>... parameterTypes ) throws ReflectiveOperationException {
104+ Method method = JavaDebugServer .class .getDeclaredMethod (name , parameterTypes );
105+ method .setAccessible (true );
106+ return method ;
107+ }
108+
109+ private static VirtualMachine fakeVm (String defaultStratum , List <ReferenceType > allClasses ) {
110+ return (VirtualMachine )
111+ Proxy .newProxyInstance (
112+ VirtualMachine .class .getClassLoader (),
113+ new Class <?>[] {VirtualMachine .class },
114+ (proxy , method , args ) -> {
115+ switch (method .getName ()) {
116+ case "allClasses" :
117+ return allClasses ;
118+ case "getDefaultStratum" :
119+ return defaultStratum ;
120+ case "toString" :
121+ return "fakeVm" ;
122+ default :
123+ throw new UnsupportedOperationException (method .getName ());
124+ }
125+ });
126+ }
127+
128+ private static ReferenceType fakeType (String name , String sourcePath ) {
129+ return (ReferenceType )
130+ Proxy .newProxyInstance (
131+ ReferenceType .class .getClassLoader (),
132+ new Class <?>[] {ReferenceType .class },
133+ (proxy , method , args ) -> {
134+ switch (method .getName ()) {
135+ case "name" :
136+ return name ;
137+ case "sourcePaths" :
138+ return List .of (sourcePath );
139+ case "toString" :
140+ return name ;
141+ default :
142+ throw new UnsupportedOperationException (method .getName ());
143+ }
144+ });
145+ }
146+
147+ private static ReferenceType fakeIrrelevantType (String name ) {
148+ return (ReferenceType )
149+ Proxy .newProxyInstance (
150+ ReferenceType .class .getClassLoader (),
151+ new Class <?>[] {ReferenceType .class },
152+ (proxy , method , args ) -> {
153+ switch (method .getName ()) {
154+ case "name" :
155+ return name ;
156+ case "sourcePaths" :
157+ throw new AssertionError ("Irrelevant classes should not need source paths" );
158+ case "toString" :
159+ return name ;
160+ default :
161+ throw new UnsupportedOperationException (method .getName ());
162+ }
163+ });
164+ }
165+
166+ private static <T > T invoke (Method method , Object target , Object ... args ) throws ReflectiveOperationException {
167+ try {
168+ @ SuppressWarnings ("unchecked" )
169+ T result = (T ) method .invoke (target , args );
170+ return result ;
171+ } catch (InvocationTargetException e ) {
172+ if (e .getCause () instanceof RuntimeException ) {
173+ throw (RuntimeException ) e .getCause ();
174+ }
175+ if (e .getCause () instanceof Error ) {
176+ throw (Error ) e .getCause ();
177+ }
178+ throw e ;
179+ }
180+ }
181+
84182 @ Test
85183 public void attachToProcess () throws IOException , InterruptedException {
86184 launchProcess ("Hello" );
@@ -89,6 +187,32 @@ public void attachToProcess() throws IOException, InterruptedException {
89187 process .waitFor ();
90188 }
91189
190+ @ Test
191+ public void loadedTypesMatchingSkipsIrrelevantClasses () throws ReflectiveOperationException {
192+ var relevant = fakeType ("com.example.Hello" , "com/example/Hello.java" );
193+ var irrelevant = fakeIrrelevantType ("org.other.Bar" );
194+ setVm (server , fakeVm ("Java" , List .of (relevant , irrelevant )));
195+
196+ var loadedTypesMatching = privateMethod ("loadedTypesMatching" , String .class );
197+ List <ReferenceType > matches = invoke (loadedTypesMatching , server , "/tmp/src/com/example/Hello.java" );
198+ org .junit .Assert .assertEquals (1 , matches .size ());
199+ org .junit .Assert .assertSame (relevant , matches .get (0 ));
200+ }
201+
202+ @ Test
203+ public void enablePendingBreakpointsInLoadedClassesSkipsIrrelevantClasses () throws ReflectiveOperationException {
204+ var pending = new Breakpoint ();
205+ pending .source = new Source ();
206+ pending .source .path = "/tmp/src/com/example/Hello.java" ;
207+ pending .line = 4 ;
208+ pendingBreakpoints (server ).add (pending );
209+ setVm (server , fakeVm ("Java" , List .of (fakeIrrelevantType ("org.other.Bar" ))));
210+
211+ invoke (privateMethod ("enablePendingBreakpointsInLoadedClasses" ), server );
212+
213+ org .junit .Assert .assertEquals (1 , pendingBreakpoints (server ).size ());
214+ }
215+
92216 @ Test
93217 public void setBreakpoint () throws IOException , InterruptedException {
94218 launchProcess ("Hello" );
0 commit comments