MapEntry renderer - now render separately key and value with appropriate renderers instead of evaluating their toStrings()

This commit is contained in:
Eugene Zhuravlev
2011-05-18 13:46:26 +02:00
parent 14693cda9c
commit 0e2ab30850
12 changed files with 193 additions and 100 deletions
@@ -15,23 +15,28 @@
*/
package com.intellij.debugger.settings;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.engine.evaluation.*;
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
import com.intellij.debugger.ui.impl.watch.WatchItemDescriptor;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.debugger.ui.tree.render.*;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.util.EventDispatcher;
import com.intellij.util.containers.InternalIterator;
import com.sun.jdi.Value;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -73,7 +78,7 @@ public class NodeRendererSettings implements PersistentStateComponent<Element> {
),
createCompoundReferenceRenderer(
"Map.Entry", "java.util.Map$Entry",
createLabelRenderer(null, "\" \" + getKey() + \" -> \" + getValue()", null),
new MapEntryLabelRenderer()/*createLabelRenderer(null, "\" \" + getKey() + \" -> \" + getValue()", null)*/,
createEnumerationChildrenRenderer(new String[][]{{"key", "getKey()"}, {"value", "getValue()"}})
),
createCompoundReferenceRenderer(
@@ -326,7 +331,7 @@ public class NodeRendererSettings implements PersistentStateComponent<Element> {
}
private CompoundReferenceRenderer createCompoundReferenceRenderer(
@NonNls final String rendererName, @NonNls final String className, final LabelRenderer labelRenderer, final ChildrenRenderer childrenRenderer
@NonNls final String rendererName, @NonNls final String className, final ValueLabelRenderer labelRenderer, final ChildrenRenderer childrenRenderer
) {
CompoundReferenceRenderer renderer = new CompoundReferenceRenderer(this, rendererName, labelRenderer, childrenRenderer);
renderer.setClassName(className);
@@ -374,4 +379,121 @@ public class NodeRendererSettings implements PersistentStateComponent<Element> {
return labelRenderer;
}
private static class MapEntryLabelRenderer extends ReferenceRenderer implements ValueLabelRenderer{
private static final Computable<String> NULL_LABEL_COMPUTABLE = new Computable<String>() {
@Override
public String compute() {
return "null";
}
};
private final MyCachedEvaluator myKeyExpression = new MyCachedEvaluator();
private final MyCachedEvaluator myValueExpression = new MyCachedEvaluator();
private MapEntryLabelRenderer() {
super("java.util.Map$Entry");
myKeyExpression.setReferenceExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, "this.getKey()"));
myValueExpression.setReferenceExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, "this.getValue()"));
}
public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener) throws EvaluateException {
final DescriptorUpdater descriptorUpdater = new DescriptorUpdater(descriptor, listener);
final Value originalValue = descriptor.getValue();
final Pair<Computable<String>, ValueDescriptorImpl> keyPair = createValueComputable(evaluationContext, originalValue, myKeyExpression, descriptorUpdater);
final Pair<Computable<String>, ValueDescriptorImpl> valuePair = createValueComputable(evaluationContext, originalValue, myValueExpression, descriptorUpdater);
descriptorUpdater.setKeyDescriptor(keyPair.second);
descriptorUpdater.setValueDescriptor(valuePair.second);
return DescriptorUpdater.constructLabelText(keyPair.first.compute(), valuePair.first.compute());
}
private Pair<Computable<String>, ValueDescriptorImpl> createValueComputable(final EvaluationContext evaluationContext,
Value originalValue,
final MyCachedEvaluator evaluator,
final DescriptorLabelListener listener) throws EvaluateException {
final Value eval = doEval(evaluationContext, originalValue, evaluator);
if (eval != null) {
final WatchItemDescriptor evalDescriptor = new WatchItemDescriptor(evaluationContext.getProject(), evaluator.getReferenceExpression(), eval);
evalDescriptor.setShowIdLabel(false);
return new Pair<Computable<String>, ValueDescriptorImpl>(new Computable<String>() {
public String compute() {
evalDescriptor.updateRepresentation((EvaluationContextImpl)evaluationContext, listener);
return evalDescriptor.getValueLabel();
}
}, evalDescriptor);
}
return new Pair<Computable<String>, ValueDescriptorImpl>(NULL_LABEL_COMPUTABLE, null);
}
public String getUniqueId() {
return "MapEntry renderer";
}
private Value doEval(EvaluationContext evaluationContext, Value originalValue, MyCachedEvaluator cachedEvaluator)
throws EvaluateException {
final DebugProcess debugProcess = evaluationContext.getDebugProcess();
if (originalValue == null) {
return null;
}
try {
final ExpressionEvaluator evaluator = cachedEvaluator.getEvaluator(debugProcess.getProject());
if(!debugProcess.isAttached()) {
throw EvaluateExceptionUtil.PROCESS_EXITED;
}
final EvaluationContext thisEvaluationContext = evaluationContext.createEvaluationContext(originalValue);
return evaluator.evaluate(thisEvaluationContext);
}
catch (final EvaluateException ex) {
throw new EvaluateException(DebuggerBundle.message("error.unable.to.evaluate.expression") + " " + ex.getMessage(), ex);
}
}
private class MyCachedEvaluator extends CachedEvaluator {
protected String getClassName() {
return MapEntryLabelRenderer.this.getClassName();
}
public ExpressionEvaluator getEvaluator(Project project) throws EvaluateException {
return super.getEvaluator(project);
}
}
}
private static class DescriptorUpdater implements DescriptorLabelListener {
private final ValueDescriptor myTargetDescriptor;
@Nullable
private ValueDescriptorImpl myKeyDescriptor;
@Nullable
private ValueDescriptorImpl myValueDescriptor;
private final DescriptorLabelListener myDelegate;
private DescriptorUpdater(ValueDescriptor descriptor, DescriptorLabelListener delegate) {
myTargetDescriptor = descriptor;
myDelegate = delegate;
}
public void setKeyDescriptor(@Nullable ValueDescriptorImpl keyDescriptor) {
myKeyDescriptor = keyDescriptor;
}
public void setValueDescriptor(@Nullable ValueDescriptorImpl valueDescriptor) {
myValueDescriptor = valueDescriptor;
}
public void labelChanged() {
myTargetDescriptor.setValueLabel(constructLabelText(getDescriptorLabel(myKeyDescriptor), getDescriptorLabel(myValueDescriptor)));
myDelegate.labelChanged();
}
static String constructLabelText(final String keylabel, final String valueLabel) {
return keylabel + " -> " + valueLabel;
}
private static String getDescriptorLabel(final ValueDescriptorImpl keyDescriptor) {
return keyDescriptor == null? "null" : keyDescriptor.getValueLabel();
}
}
}
@@ -18,7 +18,6 @@ package com.intellij.debugger.ui.impl.watch;
import com.intellij.debugger.DebuggerContext;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiExpression;
import com.sun.jdi.ClassNotLoadedException;
@@ -69,10 +68,6 @@ public class MethodReturnValueDescriptorImpl extends ValueDescriptorImpl{
throw new EvaluateException("Evaluation not supported for method return value");
}
protected String calcRepresentation(EvaluationContextImpl context, DescriptorLabelListener labelListener) {
return super.calcRepresentation(context, labelListener);
}
public boolean canSetValue() {
return false;
}
@@ -29,17 +29,16 @@ import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.settings.NodeRendererSettings;
import com.intellij.debugger.ui.tree.NodeDescriptor;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup;
import com.intellij.debugger.ui.tree.render.ClassRenderer;
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener;
import com.intellij.debugger.ui.tree.render.NodeRenderer;
import com.intellij.debugger.ui.tree.render.Renderer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiExpression;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup;
import com.sun.jdi.*;
import org.jetbrains.annotations.Nullable;
@@ -64,7 +63,8 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
private boolean myIsDirty = false;
private boolean myIsLvalue = false;
private boolean myIsExpandable;
public static final int MAX_DISPLAY_LABEL_LENGTH = 1024/*kb*/ *1024 /*bytes*/ / 2; // 1 Mb string
private boolean myShowIdLabel = true;
protected ValueDescriptorImpl(Project project, Value value) {
myProject = project;
@@ -99,6 +99,14 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
return myValueException == null;
}
public boolean isShowIdLabel() {
return myShowIdLabel;
}
public void setShowIdLabel(boolean showIdLabel) {
myShowIdLabel = showIdLabel;
}
public Value getValue() {
// the following code makes sence only if we do not use ObjectReference.enableCollection() / disableCollection()
// to keep temporary objects
@@ -211,23 +219,41 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
myValue = ((ValueDescriptorImpl)oldDescriptor).getValue();
}
protected void setLvalue (boolean value) {
protected void setLvalue(boolean value) {
myIsLvalue = value;
}
protected String calcRepresentation(EvaluationContextImpl context, DescriptorLabelListener labelListener){
myIsExpandable = (myValueException == null || myValueException.getExceptionFromTargetVM() != null) && getRenderer(context.getDebugProcess()).isExpandable(getValue(), context, this);
DebuggerManagerThreadImpl.assertIsManagerThread();
return setValueLabel(calcValueLabel(context, labelListener));
final NodeRenderer renderer = getRenderer(context.getDebugProcess());
final EvaluateException valueException = myValueException;
myIsExpandable = (valueException == null || valueException.getExceptionFromTargetVM() != null) && renderer.isExpandable(getValue(), context, this);
String label;
if (valueException == null) {
try {
label = renderer.calcLabel(this, context, labelListener);
}
catch (EvaluateException e) {
label = setValueLabelFailed(e);
}
}
else {
label = setValueLabelFailed(valueException);
}
return setValueLabel(label);
}
private String getCustomLabel(String label) {
//translate only strings in quotes
final StringBuilder buf = StringBuilderSpinAllocator.alloc();
try {
if(getValue() instanceof ObjectReference) {
final ObjectReference value = (ObjectReference)getValue();
final String idLabel = value != null? getIdLabel(value) : "";
final Value value = getValue();
if(isShowIdLabel() && value instanceof ObjectReference) {
final String idLabel = getIdLabel((ObjectReference)value);
if(!label.startsWith(idLabel)) {
buf.append(idLabel);
}
@@ -237,24 +263,7 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
buf.append("null");
}
else {
final boolean isQuoted = label.length() > 1 && StringUtil.startsWithChar(label, '\"') && StringUtil.endsWithChar(label, '\"');
if(isQuoted) {
label = label.substring(1, label.length() - 1);
buf.append('"');
}
if (label.length() > MAX_DISPLAY_LABEL_LENGTH) {
label = DebuggerUtils.translateStringValue(label.substring(0, MAX_DISPLAY_LABEL_LENGTH));
buf.append(label);
if (!label.endsWith("...")) {
buf.append("...");
}
}
else {
buf.append(DebuggerUtils.translateStringValue(label));
}
if (isQuoted) {
buf.append('"');
}
buf.append(label);
}
return buf.toString();
}
@@ -278,19 +287,6 @@ public abstract class ValueDescriptorImpl extends NodeDescriptorImpl implements
public abstract String calcValueName();
private String calcValueLabel(EvaluationContextImpl evaluationContext, final DescriptorLabelListener labelListener) {
DebuggerManagerThreadImpl.assertIsManagerThread();
try {
if(myValueException != null) {
throw myValueException;
}
return getRenderer(evaluationContext.getDebugProcess()).calcLabel(this, evaluationContext, labelListener);
}
catch (EvaluateException e) {
return setValueLabelFailed(e);
}
}
public void displayAs(NodeDescriptor descriptor) {
if (descriptor instanceof ValueDescriptorImpl) {
ValueDescriptorImpl valueDescriptor = (ValueDescriptorImpl)descriptor;
@@ -28,7 +28,7 @@ public interface ValueDescriptor extends NodeDescriptor{
Value getValue();
String setValueLabel(String customLabel);
String setValueLabel(String label);
String setValueLabelFailed(EvaluateException e);
@@ -98,7 +98,7 @@ public class ClassRenderer extends NodeRendererImpl{
try {
if (value instanceof StringReference) {
buf.append('\"');
buf.append(((StringReference)value).value());
buf.append(DebuggerUtils.convertToPresentationString(((StringReference)value).value()));
buf.append('\"');
}
else if (value instanceof ClassObjectReference) {
@@ -98,8 +98,7 @@ public final class EnumerationChildrenRenderer extends com.intellij.debugger.ui.
NodeDescriptorFactory descriptorFactory = builder.getDescriptorManager();
List<DebuggerTreeNode> children = new ArrayList<DebuggerTreeNode>();
for (Iterator<Pair<String, TextWithImports>> iterator = myChildren.iterator(); iterator.hasNext();) {
Pair<String, TextWithImports> pair = iterator.next();
for (Pair<String, TextWithImports> pair : myChildren) {
children.add(nodeManager.createNode(descriptorFactory.getUserExpressionDescriptor(
builder.getParentDescriptor(),
new UserExpressionData((ValueDescriptorImpl)builder.getParentDescriptor(), getClassName(), pair.getFirst(), pair.getSecond())), evaluationContext)
@@ -16,14 +16,10 @@
package com.intellij.debugger.ui.tree.render;
import com.intellij.debugger.DebuggerContext;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.ui.tree.DebuggerTreeNode;
import com.intellij.debugger.ui.tree.NodeDescriptor;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.debugger.ui.tree.render.ChildrenBuilder;
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener;
import com.intellij.debugger.ui.tree.render.NodeRendererImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiExpression;
import com.sun.jdi.*;
@@ -61,7 +57,7 @@ public class HexRenderer extends NodeRendererImpl{
@SuppressWarnings({"HardCodedStringLiteral"})
public String calcLabel(ValueDescriptor valueDescriptor, EvaluationContext evaluationContext, DescriptorLabelListener labelListener) {
Value value = valueDescriptor.getValue();
StringBuffer buf = new StringBuffer(16);
StringBuilder buf = new StringBuilder(16);
if(value == null) {
buf.append("null");
@@ -15,6 +15,7 @@
*/
package com.intellij.debugger.ui.tree.render;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.engine.DebugProcess;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.evaluation.EvaluateException;
@@ -23,7 +24,6 @@ import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.evaluation.TextWithImports;
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.DefaultJDOMExternalizer;
import com.intellij.openapi.util.InvalidDataException;
@@ -62,11 +62,12 @@ public class LabelRenderer extends com.intellij.debugger.ui.tree.render.Referenc
public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener labelListener)
throws EvaluateException {
String result;
LOG.assertTrue(!(descriptor instanceof PrimitiveValue));
final DebugProcess debugProcess = evaluationContext.getDebugProcess();
final Value value = descriptor.getValue();
LOG.assertTrue(!(value instanceof PrimitiveValue));
String result;
final DebugProcess debugProcess = evaluationContext.getDebugProcess();
if (value != null) {
try {
final ExpressionEvaluator evaluator = myLabelExpression.getEvaluator(debugProcess.getProject());
@@ -76,7 +77,7 @@ public class LabelRenderer extends com.intellij.debugger.ui.tree.render.Referenc
}
EvaluationContext thisEvaluationContext = evaluationContext.createEvaluationContext(value);
Value labelValue = evaluator.evaluate(thisEvaluationContext);
result = DebuggerUtils.getValueAsString(thisEvaluationContext, labelValue);
result = DebuggerUtils.convertToPresentationString(DebuggerUtils.getValueAsString(thisEvaluationContext, labelValue));
}
catch (final EvaluateException ex) {
throw new EvaluateException(DebuggerBundle.message("error.unable.to.evaluate.expression") + " " + ex.getMessage(), ex);
@@ -1,32 +0,0 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.debugger.ui.tree.render;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.options.UnnamedConfigurable;
import com.intellij.openapi.project.Project;
import java.util.List;
public interface RendererProvider extends ApplicationComponent{
Renderer createRenderer(String category);
List<String> getAvailableNodeRenderers();
UnnamedConfigurable getRendererConfigurable(Project project, NodeRenderer renderer);
boolean isSingle(NodeRenderer renderer);
}
@@ -18,6 +18,7 @@ package com.intellij.debugger.ui.tree.render;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerContext;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.impl.DebuggerUtilsEx;
@@ -73,7 +74,8 @@ public class ToStringRenderer extends NodeRendererImpl {
final Value value = valueDescriptor.getValue();
BatchEvaluator.getBatchEvaluator(evaluationContext.getDebugProcess()).invoke(new ToStringCommand(evaluationContext, value) {
public void evaluationResult(String message) {
valueDescriptor.setValueLabel(message != null ? "\"" + DebuggerUtilsEx.truncateString(message) + "\"" : "");
final String truncated = DebuggerUtilsEx.truncateString(message);
valueDescriptor.setValueLabel(message != null ? "\"" + DebuggerUtils.convertToPresentationString(truncated) + "\"" : "");
labelListener.labelChanged();
}
@@ -25,6 +25,5 @@ import com.intellij.debugger.ui.tree.ValueDescriptor;
* Time: 10:12:39 PM
*/
public interface ValueLabelRenderer extends Renderer {
String calcLabel (ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener)
throws EvaluateException;
String calcLabel (ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener) throws EvaluateException;
}
@@ -121,6 +121,21 @@ public abstract class DebuggerUtils {
}
}
public static final int MAX_DISPLAY_LABEL_LENGTH = 1024/*kb*/ *1024 /*bytes*/ / 2; // 1 Mb string
public static String convertToPresentationString(String str) {
if (str.length() > MAX_DISPLAY_LABEL_LENGTH) {
str = translateStringValue(str.substring(0, MAX_DISPLAY_LABEL_LENGTH));
StringBuilder buf = new StringBuilder();
buf.append(str);
if (!str.endsWith("...")) {
buf.append("...");
}
return buf.toString();
}
return translateStringValue(str);
}
@Nullable
public static Method findMethod(ReferenceType refType, @NonNls String methodName, @NonNls String methodSignature) {
if (refType instanceof ArrayType) {