13
13
import java .nio .file .Path ;
14
14
import java .nio .file .Paths ;
15
15
import java .util .ArrayList ;
16
+ import java .util .HashMap ;
16
17
import java .util .HashSet ;
17
18
import java .util .List ;
18
19
import java .util .Objects ;
@@ -440,6 +441,7 @@ public void terminate(TerminateArguments req) {
440
441
441
442
@ Override
442
443
public void continue_ (ContinueArguments req ) {
444
+ valueIdTracker .clear ();
443
445
vm .resume ();
444
446
}
445
447
@@ -454,6 +456,7 @@ public void next(NextArguments req) {
454
456
var step = vm .eventRequestManager ().createStepRequest (thread , StepRequest .STEP_LINE , StepRequest .STEP_OVER );
455
457
step .addCountFilter (1 );
456
458
step .enable ();
459
+ valueIdTracker .clear ();
457
460
vm .resume ();
458
461
}
459
462
@@ -468,6 +471,7 @@ public void stepIn(StepInArguments req) {
468
471
var step = vm .eventRequestManager ().createStepRequest (thread , StepRequest .STEP_LINE , StepRequest .STEP_INTO );
469
472
step .addCountFilter (1 );
470
473
step .enable ();
474
+ valueIdTracker .clear ();
471
475
vm .resume ();
472
476
}
473
477
@@ -482,6 +486,7 @@ public void stepOut(StepOutArguments req) {
482
486
var step = vm .eventRequestManager ().createStepRequest (thread , StepRequest .STEP_LINE , StepRequest .STEP_OUT );
483
487
step .addCountFilter (1 );
484
488
step .enable ();
489
+ valueIdTracker .clear ();
485
490
vm .resume ();
486
491
}
487
492
@@ -632,47 +637,157 @@ private com.sun.jdi.StackFrame findFrame(long id) {
632
637
@ Override
633
638
public ScopesResponseBody scopes (ScopesArguments req ) {
634
639
var resp = new ScopesResponseBody ();
640
+ var fields = new Scope ();
641
+ fields .name = "Fields" ;
642
+ fields .presentationHint = "locals" ;
643
+ fields .expensive = true ; // do not expand by default
644
+ fields .variablesReference = req .frameId * 2 ;
635
645
var locals = new Scope ();
636
646
locals .name = "Locals" ;
637
647
locals .presentationHint = "locals" ;
638
- locals .variablesReference = req .frameId * 2 ;
639
- var arguments = new Scope ();
640
- arguments .name = "Arguments" ;
641
- arguments .presentationHint = "arguments" ;
642
- arguments .variablesReference = req .frameId * 2 + 1 ;
643
- resp .scopes = new Scope [] {locals , arguments };
648
+ locals .variablesReference = req .frameId * 2 + 1 ;
649
+ resp .scopes = new Scope [] {fields , locals };
644
650
return resp ;
645
651
}
646
652
653
+ private static final long VALUE_ID_START = 1000000000 ;
654
+
655
+ private static class ValueIdTracker {
656
+ private final HashMap <Long , Value > values = new HashMap <>();
657
+ private long nextId = VALUE_ID_START ;
658
+
659
+ public void clear () {
660
+ values .clear ();
661
+ // Keep nextId to avoid accidentally accessing wrong Values.
662
+ }
663
+
664
+ public Value get (long id ) {
665
+ return values .get (id );
666
+ }
667
+
668
+ public long put (Value value ) {
669
+ long id = nextId ++;
670
+ values .put (id , value );
671
+ return id ;
672
+ }
673
+ }
674
+
675
+ private final ValueIdTracker valueIdTracker = new ValueIdTracker ();
676
+
677
+ private static boolean hasInterestingChildren (Value value ) {
678
+ return value instanceof ObjectReference && !(value instanceof StringReference );
679
+ }
680
+
647
681
@ Override
648
682
public VariablesResponseBody variables (VariablesArguments req ) {
649
- var frameId = req .variablesReference / 2 ;
650
- var scopeId = (int ) (req .variablesReference % 2 );
651
- var argumentScope = scopeId == 1 ;
683
+ if (req .variablesReference < VALUE_ID_START ) {
684
+ var frameId = req .variablesReference / 2 ;
685
+ var scopeId = (int )(req .variablesReference % 2 );
686
+ return frameVariables (frameId , scopeId );
687
+ }
688
+ Value value = valueIdTracker .get (req .variablesReference );
689
+ return valueChildren (value );
690
+ }
691
+
692
+ private VariablesResponseBody frameVariables (long frameId , int scopeId ) {
652
693
var frame = findFrame (frameId );
694
+ var thread = frame .thread ();
695
+ var variables = new ArrayList <Variable >();
696
+
697
+ if (scopeId == 0 ) {
698
+ var thisValue = frame .thisObject ();
699
+ if (thisValue != null ) {
700
+ variables .addAll (objectFieldsAsVariables (thisValue , thread ));
701
+ }
702
+ } else {
703
+ variables .addAll (frameLocalsAsVariables (frame , thread ));
704
+ }
705
+
706
+ var resp = new VariablesResponseBody ();
707
+ resp .variables = variables .toArray (Variable []::new );
708
+ return resp ;
709
+ }
710
+
711
+ private VariablesResponseBody valueChildren (Value parentValue ) {
712
+ // TODO: Use an actual owner thread.
713
+ ThreadReference mainThread = vm .allThreads ().get (0 );
714
+ var variables = new ArrayList <Variable >();
715
+
716
+ if (parentValue instanceof ArrayReference array ) {
717
+ variables .addAll (arrayElementsAsVariables (array , mainThread ));
718
+ } else if (parentValue instanceof ObjectReference object ) {
719
+ variables .addAll (objectFieldsAsVariables (object , mainThread ));
720
+ }
721
+
722
+ var resp = new VariablesResponseBody ();
723
+ resp .variables = variables .toArray (Variable []::new );
724
+ return resp ;
725
+ }
726
+
727
+ private List <Variable > frameLocalsAsVariables (com .sun .jdi .StackFrame frame , ThreadReference thread ) {
653
728
List <LocalVariable > visible ;
654
729
try {
655
730
visible = frame .visibleVariables ();
656
731
} catch (AbsentInformationException __ ) {
657
732
LOG .warning (String .format ("No visible variable information in %s" , frame .location ()));
658
- return new VariablesResponseBody ();
733
+ return List . of ();
659
734
}
660
- var values = frame .getValues (visible );
661
- var thread = frame .thread ();
735
+
662
736
var variables = new ArrayList <Variable >();
737
+ var values = frame .getValues (visible );
663
738
for (var v : visible ) {
664
- if ( v . isArgument () != argumentScope ) continue ;
739
+ var value = values . get ( v ) ;
665
740
var w = new Variable ();
666
741
w .name = v .name ();
667
- w .value = print (values . get ( v ) , thread );
742
+ w .value = print (value , thread );
668
743
w .type = v .typeName ();
669
- // TODO set variablesReference and allow inspecting structure of collections and POJOs
744
+ if (hasInterestingChildren (value )) {
745
+ w .variablesReference = valueIdTracker .put (value );
746
+ }
747
+ if (value instanceof ArrayReference array ) {
748
+ w .indexedVariables = array .length ();
749
+ }
670
750
// TODO set variablePresentationHint
671
751
variables .add (w );
672
752
}
673
- var resp = new VariablesResponseBody ();
674
- resp .variables = variables .toArray (Variable []::new );
675
- return resp ;
753
+ return variables ;
754
+ }
755
+
756
+ private List <Variable > arrayElementsAsVariables (ArrayReference array , ThreadReference thread ) {
757
+ var variables = new ArrayList <Variable >();
758
+ var arrayType = (ArrayType ) array .type ();
759
+ var values = array .getValues ();
760
+ var length = values .size ();
761
+ for (int i = 0 ; i < length ; i ++) {
762
+ var value = values .get (i );
763
+ var w = new Variable ();
764
+ w .name = Integer .toString (i , 10 );
765
+ w .value = print (value , thread );
766
+ w .type = arrayType .componentTypeName ();
767
+ if (hasInterestingChildren (value )) {
768
+ w .variablesReference = valueIdTracker .put (value );
769
+ }
770
+ variables .add (w );
771
+ }
772
+ return variables ;
773
+ }
774
+
775
+ private List <Variable > objectFieldsAsVariables (ObjectReference object , ThreadReference thread ) {
776
+ var variables = new ArrayList <Variable >();
777
+ var classType = (ClassType ) object .type ();
778
+ var values = object .getValues (classType .allFields ());
779
+ for (var field : values .keySet ()) {
780
+ var value = values .get (field );
781
+ var w = new Variable ();
782
+ w .name = field .name ();
783
+ w .value = print (value , thread );
784
+ w .type = field .typeName ();
785
+ if (hasInterestingChildren (value )) {
786
+ w .variablesReference = valueIdTracker .put (value );
787
+ }
788
+ variables .add (w );
789
+ }
790
+ return variables ;
676
791
}
677
792
678
793
private String print (Value value , ThreadReference t ) {
0 commit comments