Skip to content

Commit 8ca4cc9

Browse files
Variable: Show toString() value for the classes that override toString method (#276)
* Variable: Show toString() value for the class that override toString method Signed-off-by: Jinbo Wang <[email protected]> * Refactor size details logic Signed-off-by: Jinbo Wang <[email protected]> * Address review comments Signed-off-by: Jinbo Wang <[email protected]>
1 parent d91322d commit 8ca4cc9

File tree

10 files changed

+200
-24
lines changed

10 files changed

+200
-24
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class DebugSettings {
2626
public boolean showQualifiedNames = false;
2727
public boolean showHex = false;
2828
public boolean showLogicalStructure = true;
29+
public boolean showToString = true;
2930
public String logLevel;
3031
public String javaHome;
3132
public HotCodeReplace hotCodeReplace = HotCodeReplace.MANUAL;

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2019 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -39,7 +39,7 @@ public ObjectFormatter(BiFunction<Type, Map<String, Object>, String> typeToStrin
3939

4040
@Override
4141
public String toString(Object obj, Map<String, Object> options) {
42-
return String.format("%s %s", getPrefix((ObjectReference) obj, options),
42+
return String.format("%s@%s", getPrefix((ObjectReference) obj, options),
4343
getIdPostfix((ObjectReference) obj, options));
4444
}
4545

@@ -74,6 +74,6 @@ protected String getPrefix(ObjectReference value, Map<String, Object> options) {
7474
}
7575

7676
protected static String getIdPostfix(ObjectReference obj, Map<String, Object> options) {
77-
return String.format("(id=%s)", NumericFormatter.formatNumber(obj.uniqueID(), options));
77+
return NumericFormatter.formatNumber(obj.uniqueID(), options);
7878
}
7979
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2019 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -19,7 +19,6 @@
1919

2020
import org.apache.commons.lang3.StringUtils;
2121

22-
import com.sun.jdi.ObjectReference;
2322
import com.sun.jdi.StringReference;
2423
import com.sun.jdi.Type;
2524
import com.sun.jdi.Value;
@@ -43,9 +42,8 @@ public Map<String, Object> getDefaultOptions() {
4342
@Override
4443
public String toString(Object value, Map<String, Object> options) {
4544
int maxLength = getMaxStringLength(options);
46-
return String.format("\"%s\" %s",
47-
maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value(),
48-
getIdPostfix((ObjectReference) value, options));
45+
return String.format("\"%s\"",
46+
maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value());
4947
}
5048

5149
@Override

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017 Microsoft Corporation and others.
2+
* Copyright (c) 2017-2019 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -34,6 +34,7 @@
3434
import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter;
3535
import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager;
3636
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
37+
import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils;
3738
import com.microsoft.java.debug.core.adapter.variables.VariableProxy;
3839
import com.microsoft.java.debug.core.adapter.variables.VariableUtils;
3940
import com.microsoft.java.debug.core.protocol.Messages.Response;
@@ -89,13 +90,14 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
8990
if (value instanceof ObjectReference) {
9091
VariableProxy varProxy = new VariableProxy(stackFrameReference.getThread(), "eval", value);
9192
int indexedVariables = -1;
93+
Value sizeValue = null;
9294
if (value instanceof ArrayReference) {
9395
indexedVariables = ((ArrayReference) value).length();
9496
} else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure
9597
&& engine != null
9698
&& JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) {
9799
try {
98-
Value sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, stackFrameReference.getThread(), engine);
100+
sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, stackFrameReference.getThread(), engine);
99101
if (sizeValue != null && sizeValue instanceof IntegerValue) {
100102
indexedVariables = ((IntegerValue) sizeValue).value();
101103
}
@@ -110,7 +112,15 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
110112
referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy);
111113
}
112114

113-
response.body = new Responses.EvaluateResponseBody(variableFormatter.valueToString(value, options),
115+
String valueString = variableFormatter.valueToString(value, options);
116+
String detailsString = null;
117+
if (sizeValue != null) {
118+
detailsString = "size=" + variableFormatter.valueToString(sizeValue, options);
119+
} else if (DebugSettings.getCurrent().showToString) {
120+
detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine);
121+
}
122+
123+
response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString,
114124
referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options),
115125
Math.max(indexedVariables, 0));
116126
return response;

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager;
4242
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
4343
import com.microsoft.java.debug.core.adapter.variables.Variable;
44+
import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils;
4445
import com.microsoft.java.debug.core.adapter.variables.VariableProxy;
4546
import com.microsoft.java.debug.core.adapter.variables.VariableUtils;
4647
import com.microsoft.java.debug.core.protocol.Messages.Response;
@@ -76,6 +77,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
7677

7778
Map<String, Object> options = variableFormatter.getDefaultOptions();
7879
VariableUtils.applyFormatterOptions(options, varArgs.format != null && varArgs.format.hex);
80+
IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class);
7981

8082
List<Types.Variable> list = new ArrayList<>();
8183
Object container = context.getRecyclableIdPool().getObjectById(varArgs.variablesReference);
@@ -121,7 +123,6 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
121123
} else {
122124
try {
123125
ObjectReference containerObj = (ObjectReference) containerNode.getProxiedVariable();
124-
IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class);
125126
if (DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) {
126127
JavaLogicalStructure logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj);
127128
while (logicalStructure != null) {
@@ -211,14 +212,14 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
211212
name = variableNameMap.get(javaVariable);
212213
}
213214
int indexedVariables = -1;
215+
Value sizeValue = null;
214216
if (value instanceof ArrayReference) {
215217
indexedVariables = ((ArrayReference) value).length();
216218
} else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure
217-
&& context.getProvider(IEvaluationProvider.class) != null
219+
&& evaluationEngine != null
218220
&& JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) {
219-
IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class);
220221
try {
221-
Value sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, containerNode.getThread(), evaluationEngine);
222+
sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, containerNode.getThread(), evaluationEngine);
222223
if (sizeValue != null && sizeValue instanceof IntegerValue) {
223224
indexedVariables = ((IntegerValue) sizeValue).value();
224225
}
@@ -233,10 +234,21 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
233234
VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value);
234235
referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy);
235236
}
237+
236238
Types.Variable typedVariables = new Types.Variable(name, variableFormatter.valueToString(value, options),
237239
variableFormatter.typeToString(value == null ? null : value.type(), options),
238240
referenceId, null);
239241
typedVariables.indexedVariables = Math.max(indexedVariables, 0);
242+
String detailsValue = null;
243+
if (sizeValue != null) {
244+
detailsValue = "size=" + variableFormatter.valueToString(sizeValue, options);
245+
} else if (DebugSettings.getCurrent().showToString) {
246+
detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine);
247+
}
248+
249+
if (detailsValue != null) {
250+
typedVariables.value = typedVariables.value + " " + detailsValue;
251+
}
240252
list.add(typedVariables);
241253
}
242254
response.body = new Responses.VariablesResponseBody(list);
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2019 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.core.adapter.variables;
13+
14+
import java.util.Arrays;
15+
import java.util.HashSet;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Objects;
19+
import java.util.Set;
20+
import java.util.concurrent.ExecutionException;
21+
22+
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
23+
import com.sun.jdi.ClassType;
24+
import com.sun.jdi.InterfaceType;
25+
import com.sun.jdi.Method;
26+
import com.sun.jdi.ObjectReference;
27+
import com.sun.jdi.ReferenceType;
28+
import com.sun.jdi.ThreadReference;
29+
import com.sun.jdi.Type;
30+
import com.sun.jdi.Value;
31+
32+
public class VariableDetailUtils {
33+
private static final String STRING_TYPE = "java.lang.String";
34+
private static final String TO_STRING_METHOD = "toString";
35+
private static final String TO_STRING_METHOD_SIGNATURE = "()Ljava/lang/String;";
36+
private static final String ENTRY_TYPE = "java.util.Map$Entry";
37+
private static final String GET_KEY_METHOD = "getKey";
38+
private static final String GET_KEY_METHOD_SIGNATURE = "()Ljava/lang/Object;";
39+
private static final String GET_VALUE_METHOD = "getValue";
40+
private static final String GET_VALUE_METHOD_SIGNATURE = "()Ljava/lang/Object;";
41+
private static final Set<String> COLLECTION_TYPES = new HashSet(
42+
Arrays.asList("java.util.Map", "java.util.Collection", "java.util.Map$Entry"));
43+
44+
/**
45+
* Returns the details information for the specified variable.
46+
*/
47+
public static String formatDetailsValue(Value value, ThreadReference thread, IVariableFormatter variableFormatter, Map<String, Object> options,
48+
IEvaluationProvider evaluationEngine) {
49+
if (isClassType(value, STRING_TYPE)) {
50+
// No need to show additional details information.
51+
return null;
52+
} else {
53+
return computeToStringValue(value, thread, variableFormatter, options, evaluationEngine, true);
54+
}
55+
}
56+
57+
private static String computeToStringValue(Value value, ThreadReference thread, IVariableFormatter variableFormatter,
58+
Map<String, Object> options, IEvaluationProvider evaluationEngine, boolean isFirstLevel) {
59+
if (!(value instanceof ObjectReference) || evaluationEngine == null) {
60+
return null;
61+
}
62+
63+
String inheritedType = findInheritedType(value, COLLECTION_TYPES);
64+
if (inheritedType != null) {
65+
if (Objects.equals(inheritedType, ENTRY_TYPE)) {
66+
try {
67+
Value keyObject = evaluationEngine.invokeMethod((ObjectReference) value, GET_KEY_METHOD, GET_KEY_METHOD_SIGNATURE,
68+
null, thread, false).get();
69+
Value valueObject = evaluationEngine.invokeMethod((ObjectReference) value, GET_VALUE_METHOD, GET_VALUE_METHOD_SIGNATURE,
70+
null, thread, false).get();
71+
String toStringValue = computeToStringValue(keyObject, thread, variableFormatter, options, evaluationEngine, false)
72+
+ ":"
73+
+ computeToStringValue(valueObject, thread, variableFormatter, options, evaluationEngine, false);
74+
if (!isFirstLevel) {
75+
toStringValue = "\"" + toStringValue + "\"";
76+
}
77+
78+
return toStringValue;
79+
} catch (InterruptedException | ExecutionException e) {
80+
// do nothing.
81+
}
82+
} else if (!isFirstLevel) {
83+
return variableFormatter.valueToString(value, options);
84+
}
85+
} else if (containsToStringMethod((ObjectReference) value)) {
86+
try {
87+
Value toStringValue = evaluationEngine.invokeMethod((ObjectReference) value, TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE,
88+
null, thread, false).get();
89+
return variableFormatter.valueToString(toStringValue, options);
90+
} catch (InterruptedException | ExecutionException e) {
91+
// do nothing.
92+
}
93+
}
94+
95+
return null;
96+
}
97+
98+
private static boolean containsToStringMethod(ObjectReference obj) {
99+
ReferenceType refType = obj.referenceType();
100+
if (refType instanceof ClassType) {
101+
Method m = ((ClassType) refType).concreteMethodByName(TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE);
102+
if (m != null) {
103+
if (!Objects.equals("Ljava/lang/Object;", m.declaringType().signature())) {
104+
return true;
105+
}
106+
}
107+
108+
for (InterfaceType iface : ((ClassType) refType).allInterfaces()) {
109+
List<Method> matches = iface.methodsByName(TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE);
110+
for (Method ifaceMethod : matches) {
111+
if (!ifaceMethod.isAbstract()) {
112+
return true;
113+
}
114+
}
115+
}
116+
}
117+
118+
return false;
119+
}
120+
121+
private static String findInheritedType(Value value, Set<String> typeNames) {
122+
if (!(value instanceof ObjectReference)) {
123+
return null;
124+
}
125+
126+
Type variableType = ((ObjectReference) value).type();
127+
if (!(variableType instanceof ClassType)) {
128+
return null;
129+
}
130+
131+
ClassType classType = (ClassType) variableType;
132+
while (classType != null) {
133+
if (typeNames.contains(classType.name())) {
134+
return classType.name();
135+
}
136+
137+
classType = classType.superclass();
138+
}
139+
140+
List<InterfaceType> interfaceTypes = ((ClassType) variableType).allInterfaces();
141+
for (InterfaceType interfaceType : interfaceTypes) {
142+
if (typeNames.contains(interfaceType.name())) {
143+
return interfaceType.name();
144+
}
145+
}
146+
147+
return null;
148+
}
149+
150+
private static boolean isClassType(Value value, String typeName) {
151+
if (!(value instanceof ObjectReference)) {
152+
return false;
153+
}
154+
155+
return Objects.equals(((ObjectReference) value).type().name(), typeName);
156+
}
157+
}

com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void testAcceptType() throws Exception {
5858
@Test
5959
public void testToString() throws Exception {
6060
Value arrays = this.getLocalValue("arrays");
61-
assertEquals("Should be able to format array type.", String.format("int[1] (id=%d)",
61+
assertEquals("Should be able to format array type.", String.format("int[1]@%d",
6262
((ObjectReference) arrays).uniqueID()),
6363
formatter.toString(arrays, new HashMap<>()));
6464
}

com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void testValueOfNotUnsupported() {
7373
@Test
7474
public void testToString() throws Exception {
7575
Value clazzValue = this.getLocalValue("b");
76-
assertEquals("Should be able to format clazz type.", String.format("java.lang.Class (A) (id=%d)",
76+
assertEquals("Should be able to format clazz type.", String.format("java.lang.Class (A)@%d",
7777
((ObjectReference)clazzValue).uniqueID()),
7878
formatter.toString(clazzValue, new HashMap<>()));
7979
}

com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatterTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void testAcceptType() throws Exception {
6363
@Test
6464
public void testToStringDec() throws Exception {
6565
ObjectReference or = this.getObjectReference("Foo");
66-
assertEquals("Failed to format an object.", String.format("MockType (id=%d)", or.uniqueID()),
66+
assertEquals("Failed to format an object.", String.format("MockType@%d", or.uniqueID()),
6767
formatter.toString(or, new HashMap<>()));
6868
}
6969

@@ -72,7 +72,7 @@ public void testToStringHex() throws Exception {
7272
ObjectReference or = this.getObjectReference("Foo");
7373
Map<String, Object> options = formatter.getDefaultOptions();
7474
options.put(NUMERIC_FORMAT_OPTION, NumericFormatEnum.HEX);
75-
assertEquals("Failed to format an object.", String.format("MockType (id=%#x)", or.uniqueID()),
75+
assertEquals("Failed to format an object.", String.format("MockType@%#x", or.uniqueID()),
7676
formatter.toString(or, options));
7777
}
7878

@@ -81,7 +81,7 @@ public void testToStringOct() throws Exception {
8181
ObjectReference or = this.getObjectReference("Foo");
8282
Map<String, Object> options = formatter.getDefaultOptions();
8383
options.put(NUMERIC_FORMAT_OPTION, NumericFormatEnum.OCT);
84-
assertEquals("Failed to format an object.", String.format("MockType (id=%#o)", or.uniqueID()),
84+
assertEquals("Failed to format an object.", String.format("MockType@%#o", or.uniqueID()),
8585
formatter.toString(or, options));
8686
}
8787

0 commit comments

Comments
 (0)