diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java index e9d20963b..1c69f88e2 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSettings.java @@ -26,6 +26,7 @@ public final class DebugSettings { public boolean showQualifiedNames = false; public boolean showHex = false; public boolean showLogicalStructure = true; + public boolean showToString = true; public String logLevel; public String javaHome; public HotCodeReplace hotCodeReplace = HotCodeReplace.MANUAL; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java index ee974f8fe..25a44d27c 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Microsoft Corporation and others. + * Copyright (c) 2017-2019 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -39,7 +39,7 @@ public ObjectFormatter(BiFunction, String> typeToStrin @Override public String toString(Object obj, Map options) { - return String.format("%s %s", getPrefix((ObjectReference) obj, options), + return String.format("%s@%s", getPrefix((ObjectReference) obj, options), getIdPostfix((ObjectReference) obj, options)); } @@ -74,6 +74,6 @@ protected String getPrefix(ObjectReference value, Map options) { } protected static String getIdPostfix(ObjectReference obj, Map options) { - return String.format("(id=%s)", NumericFormatter.formatNumber(obj.uniqueID(), options)); + return NumericFormatter.formatNumber(obj.uniqueID(), options); } } diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java index d4c1e663d..299f7dd32 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatter.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. +* Copyright (c) 2017-2019 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -19,7 +19,6 @@ import org.apache.commons.lang3.StringUtils; -import com.sun.jdi.ObjectReference; import com.sun.jdi.StringReference; import com.sun.jdi.Type; import com.sun.jdi.Value; @@ -43,9 +42,8 @@ public Map getDefaultOptions() { @Override public String toString(Object value, Map options) { int maxLength = getMaxStringLength(options); - return String.format("\"%s\" %s", - maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value(), - getIdPostfix((ObjectReference) value, options)); + return String.format("\"%s\"", + maxLength > 0 ? StringUtils.abbreviate(((StringReference) value).value(), maxLength) : ((StringReference) value).value()); } @Override diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java index f4c516fde..30f98d7a8 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2017 Microsoft Corporation and others. +* Copyright (c) 2017-2019 Microsoft Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -34,6 +34,7 @@ import com.microsoft.java.debug.core.adapter.variables.IVariableFormatter; import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; +import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; import com.microsoft.java.debug.core.adapter.variables.VariableProxy; import com.microsoft.java.debug.core.adapter.variables.VariableUtils; import com.microsoft.java.debug.core.protocol.Messages.Response; @@ -89,13 +90,14 @@ public CompletableFuture handle(Command command, Arguments arguments, if (value instanceof ObjectReference) { VariableProxy varProxy = new VariableProxy(stackFrameReference.getThread(), "eval", value); int indexedVariables = -1; + Value sizeValue = null; if (value instanceof ArrayReference) { indexedVariables = ((ArrayReference) value).length(); } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure && engine != null && JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) { try { - Value sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, stackFrameReference.getThread(), engine); + sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, stackFrameReference.getThread(), engine); if (sizeValue != null && sizeValue instanceof IntegerValue) { indexedVariables = ((IntegerValue) sizeValue).value(); } @@ -110,7 +112,15 @@ public CompletableFuture handle(Command command, Arguments arguments, referenceId = context.getRecyclableIdPool().addObject(threadId, varProxy); } - response.body = new Responses.EvaluateResponseBody(variableFormatter.valueToString(value, options), + String valueString = variableFormatter.valueToString(value, options); + String detailsString = null; + if (sizeValue != null) { + detailsString = "size=" + variableFormatter.valueToString(sizeValue, options); + } else if (DebugSettings.getCurrent().showToString) { + detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine); + } + + response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString, referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options), Math.max(indexedVariables, 0)); return response; diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java index 8ad042fcd..45ed9fc34 100644 --- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java @@ -41,6 +41,7 @@ import com.microsoft.java.debug.core.adapter.variables.JavaLogicalStructureManager; import com.microsoft.java.debug.core.adapter.variables.StackFrameReference; import com.microsoft.java.debug.core.adapter.variables.Variable; +import com.microsoft.java.debug.core.adapter.variables.VariableDetailUtils; import com.microsoft.java.debug.core.adapter.variables.VariableProxy; import com.microsoft.java.debug.core.adapter.variables.VariableUtils; import com.microsoft.java.debug.core.protocol.Messages.Response; @@ -76,6 +77,7 @@ public CompletableFuture handle(Command command, Arguments arguments, Map options = variableFormatter.getDefaultOptions(); VariableUtils.applyFormatterOptions(options, varArgs.format != null && varArgs.format.hex); + IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class); List list = new ArrayList<>(); Object container = context.getRecyclableIdPool().getObjectById(varArgs.variablesReference); @@ -121,7 +123,6 @@ public CompletableFuture handle(Command command, Arguments arguments, } else { try { ObjectReference containerObj = (ObjectReference) containerNode.getProxiedVariable(); - IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class); if (DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) { JavaLogicalStructure logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj); while (logicalStructure != null) { @@ -211,14 +212,14 @@ public CompletableFuture handle(Command command, Arguments arguments, name = variableNameMap.get(javaVariable); } int indexedVariables = -1; + Value sizeValue = null; if (value instanceof ArrayReference) { indexedVariables = ((ArrayReference) value).length(); } else if (value instanceof ObjectReference && DebugSettings.getCurrent().showLogicalStructure - && context.getProvider(IEvaluationProvider.class) != null + && evaluationEngine != null && JavaLogicalStructureManager.isIndexedVariable((ObjectReference) value)) { - IEvaluationProvider evaluationEngine = context.getProvider(IEvaluationProvider.class); try { - Value sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, containerNode.getThread(), evaluationEngine); + sizeValue = JavaLogicalStructureManager.getLogicalSize((ObjectReference) value, containerNode.getThread(), evaluationEngine); if (sizeValue != null && sizeValue instanceof IntegerValue) { indexedVariables = ((IntegerValue) sizeValue).value(); } @@ -233,10 +234,21 @@ public CompletableFuture handle(Command command, Arguments arguments, VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value); referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy); } + Types.Variable typedVariables = new Types.Variable(name, variableFormatter.valueToString(value, options), variableFormatter.typeToString(value == null ? null : value.type(), options), referenceId, null); typedVariables.indexedVariables = Math.max(indexedVariables, 0); + String detailsValue = null; + if (sizeValue != null) { + detailsValue = "size=" + variableFormatter.valueToString(sizeValue, options); + } else if (DebugSettings.getCurrent().showToString) { + detailsValue = VariableDetailUtils.formatDetailsValue(value, containerNode.getThread(), variableFormatter, options, evaluationEngine); + } + + if (detailsValue != null) { + typedVariables.value = typedVariables.value + " " + detailsValue; + } list.add(typedVariables); } response.body = new Responses.VariablesResponseBody(list); diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java new file mode 100644 index 000000000..e82ab79f9 --- /dev/null +++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/VariableDetailUtils.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2019 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.debug.core.adapter.variables; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import com.microsoft.java.debug.core.adapter.IEvaluationProvider; +import com.sun.jdi.ClassType; +import com.sun.jdi.InterfaceType; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Type; +import com.sun.jdi.Value; + +public class VariableDetailUtils { + private static final String STRING_TYPE = "java.lang.String"; + private static final String TO_STRING_METHOD = "toString"; + private static final String TO_STRING_METHOD_SIGNATURE = "()Ljava/lang/String;"; + private static final String ENTRY_TYPE = "java.util.Map$Entry"; + private static final String GET_KEY_METHOD = "getKey"; + private static final String GET_KEY_METHOD_SIGNATURE = "()Ljava/lang/Object;"; + private static final String GET_VALUE_METHOD = "getValue"; + private static final String GET_VALUE_METHOD_SIGNATURE = "()Ljava/lang/Object;"; + private static final Set COLLECTION_TYPES = new HashSet( + Arrays.asList("java.util.Map", "java.util.Collection", "java.util.Map$Entry")); + + /** + * Returns the details information for the specified variable. + */ + public static String formatDetailsValue(Value value, ThreadReference thread, IVariableFormatter variableFormatter, Map options, + IEvaluationProvider evaluationEngine) { + if (isClassType(value, STRING_TYPE)) { + // No need to show additional details information. + return null; + } else { + return computeToStringValue(value, thread, variableFormatter, options, evaluationEngine, true); + } + } + + private static String computeToStringValue(Value value, ThreadReference thread, IVariableFormatter variableFormatter, + Map options, IEvaluationProvider evaluationEngine, boolean isFirstLevel) { + if (!(value instanceof ObjectReference) || evaluationEngine == null) { + return null; + } + + String inheritedType = findInheritedType(value, COLLECTION_TYPES); + if (inheritedType != null) { + if (Objects.equals(inheritedType, ENTRY_TYPE)) { + try { + Value keyObject = evaluationEngine.invokeMethod((ObjectReference) value, GET_KEY_METHOD, GET_KEY_METHOD_SIGNATURE, + null, thread, false).get(); + Value valueObject = evaluationEngine.invokeMethod((ObjectReference) value, GET_VALUE_METHOD, GET_VALUE_METHOD_SIGNATURE, + null, thread, false).get(); + String toStringValue = computeToStringValue(keyObject, thread, variableFormatter, options, evaluationEngine, false) + + ":" + + computeToStringValue(valueObject, thread, variableFormatter, options, evaluationEngine, false); + if (!isFirstLevel) { + toStringValue = "\"" + toStringValue + "\""; + } + + return toStringValue; + } catch (InterruptedException | ExecutionException e) { + // do nothing. + } + } else if (!isFirstLevel) { + return variableFormatter.valueToString(value, options); + } + } else if (containsToStringMethod((ObjectReference) value)) { + try { + Value toStringValue = evaluationEngine.invokeMethod((ObjectReference) value, TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE, + null, thread, false).get(); + return variableFormatter.valueToString(toStringValue, options); + } catch (InterruptedException | ExecutionException e) { + // do nothing. + } + } + + return null; + } + + private static boolean containsToStringMethod(ObjectReference obj) { + ReferenceType refType = obj.referenceType(); + if (refType instanceof ClassType) { + Method m = ((ClassType) refType).concreteMethodByName(TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE); + if (m != null) { + if (!Objects.equals("Ljava/lang/Object;", m.declaringType().signature())) { + return true; + } + } + + for (InterfaceType iface : ((ClassType) refType).allInterfaces()) { + List matches = iface.methodsByName(TO_STRING_METHOD, TO_STRING_METHOD_SIGNATURE); + for (Method ifaceMethod : matches) { + if (!ifaceMethod.isAbstract()) { + return true; + } + } + } + } + + return false; + } + + private static String findInheritedType(Value value, Set typeNames) { + if (!(value instanceof ObjectReference)) { + return null; + } + + Type variableType = ((ObjectReference) value).type(); + if (!(variableType instanceof ClassType)) { + return null; + } + + ClassType classType = (ClassType) variableType; + while (classType != null) { + if (typeNames.contains(classType.name())) { + return classType.name(); + } + + classType = classType.superclass(); + } + + List interfaceTypes = ((ClassType) variableType).allInterfaces(); + for (InterfaceType interfaceType : interfaceTypes) { + if (typeNames.contains(interfaceType.name())) { + return interfaceType.name(); + } + } + + return null; + } + + private static boolean isClassType(Value value, String typeName) { + if (!(value instanceof ObjectReference)) { + return false; + } + + return Objects.equals(((ObjectReference) value).type().name(), typeName); + } +} diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatterTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatterTest.java index d86b82d9d..bf867ec72 100644 --- a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatterTest.java +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ArrayObjectFormatterTest.java @@ -58,7 +58,7 @@ public void testAcceptType() throws Exception { @Test public void testToString() throws Exception { Value arrays = this.getLocalValue("arrays"); - assertEquals("Should be able to format array type.", String.format("int[1] (id=%d)", + assertEquals("Should be able to format array type.", String.format("int[1]@%d", ((ObjectReference) arrays).uniqueID()), formatter.toString(arrays, new HashMap<>())); } diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatterTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatterTest.java index 7ea62ecc4..9abafd48b 100644 --- a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatterTest.java +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ClassObjectFormatterTest.java @@ -73,7 +73,7 @@ public void testValueOfNotUnsupported() { @Test public void testToString() throws Exception { Value clazzValue = this.getLocalValue("b"); - assertEquals("Should be able to format clazz type.", String.format("java.lang.Class (A) (id=%d)", + assertEquals("Should be able to format clazz type.", String.format("java.lang.Class (A)@%d", ((ObjectReference)clazzValue).uniqueID()), formatter.toString(clazzValue, new HashMap<>())); } diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatterTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatterTest.java index 4f63ea607..430220d3c 100644 --- a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatterTest.java +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/ObjectFormatterTest.java @@ -63,7 +63,7 @@ public void testAcceptType() throws Exception { @Test public void testToStringDec() throws Exception { ObjectReference or = this.getObjectReference("Foo"); - assertEquals("Failed to format an object.", String.format("MockType (id=%d)", or.uniqueID()), + assertEquals("Failed to format an object.", String.format("MockType@%d", or.uniqueID()), formatter.toString(or, new HashMap<>())); } @@ -72,7 +72,7 @@ public void testToStringHex() throws Exception { ObjectReference or = this.getObjectReference("Foo"); Map options = formatter.getDefaultOptions(); options.put(NUMERIC_FORMAT_OPTION, NumericFormatEnum.HEX); - assertEquals("Failed to format an object.", String.format("MockType (id=%#x)", or.uniqueID()), + assertEquals("Failed to format an object.", String.format("MockType@%#x", or.uniqueID()), formatter.toString(or, options)); } @@ -81,7 +81,7 @@ public void testToStringOct() throws Exception { ObjectReference or = this.getObjectReference("Foo"); Map options = formatter.getDefaultOptions(); options.put(NUMERIC_FORMAT_OPTION, NumericFormatEnum.OCT); - assertEquals("Failed to format an object.", String.format("MockType (id=%#o)", or.uniqueID()), + assertEquals("Failed to format an object.", String.format("MockType@%#o", or.uniqueID()), formatter.toString(or, options)); } diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatterTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatterTest.java index 379a6feb7..63b388e1a 100644 --- a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatterTest.java +++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/adapter/formatter/StringObjectFormatterTest.java @@ -65,13 +65,11 @@ public void testToString() throws Exception { Value string = this.getLocalValue("str"); Map options = formatter.getDefaultOptions(); options.put(MAX_STRING_LENGTH_OPTION, 4); - assertEquals("Should be able to format string type.", String.format("\"s...\" (id=%d)", - ((ObjectReference) string).uniqueID()), + assertEquals("Should be able to format string type.", "\"s...\"", formatter.toString(string, options)); options.put(MAX_STRING_LENGTH_OPTION, 5); - assertEquals("Should be able to format string type.", String.format("\"st...\" (id=%d)", - ((ObjectReference) string).uniqueID()), + assertEquals("Should be able to format string type.", "\"st...\"", formatter.toString(string, options)); assertTrue("Should not trim long string by default", formatter.toString(string, new HashMap<>()).contains(((StringReference) string).value()));