blob: e1d80414f0e68f877862016213b10def1b6b00f6 [file] [log] [blame]
/*
* Copyright 2000-2012 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.settings;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerContext;
import com.intellij.debugger.DebuggerManagerEx;
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.ArrayElementDescriptorImpl;
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
import com.intellij.debugger.ui.impl.watch.WatchItemDescriptor;
import com.intellij.debugger.ui.tree.DebuggerTreeNode;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.debugger.ui.tree.render.*;
import com.intellij.debugger.ui.tree.render.Renderer;
import com.intellij.openapi.components.*;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiExpression;
import com.intellij.util.EventDispatcher;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.InternalIterator;
import com.intellij.util.ui.ColorIcon;
import com.sun.jdi.*;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* User: lex
* Date: Sep 18, 2003
* Time: 8:00:25 PM
*/
@State(
name="NodeRendererSettings",
storages= {
@Storage(
file = StoragePathMacros.APP_CONFIG + "/debugger.renderers.xml"
)}
)
public class NodeRendererSettings implements PersistentStateComponent<Element> {
@NonNls private static final String REFERENCE_RENDERER = "Reference renderer";
@NonNls public static final String RENDERER_TAG = "Renderer";
@NonNls private static final String RENDERER_ID = "ID";
private final EventDispatcher<NodeRendererSettingsListener> myDispatcher = EventDispatcher.create(NodeRendererSettingsListener.class);
private final List<NodeRenderer> myPluginRenderers = new ArrayList<NodeRenderer>();
private RendererConfiguration myCustomRenderers = new RendererConfiguration(this);
// base renderers
private final PrimitiveRenderer myPrimitiveRenderer = new PrimitiveRenderer();
private final ArrayRenderer myArrayRenderer = new ArrayRenderer();
private final ClassRenderer myClassRenderer = new ClassRenderer();
private final HexRenderer myHexRenderer = new HexRenderer();
private final ToStringRenderer myToStringRenderer = new ToStringRenderer();
private final CompoundReferenceRenderer myColorRenderer;
// alternate collections
private final NodeRenderer[] myAlternateCollectionRenderers = new NodeRenderer[]{
createCompoundReferenceRenderer(
"Map", CommonClassNames.JAVA_UTIL_MAP,
createLabelRenderer(" size = ", "size()", null),
createExpressionChildrenRenderer("entrySet().toArray()", "!isEmpty()")
),
createCompoundReferenceRenderer(
"Map.Entry", "java.util.Map$Entry",
new MapEntryLabelRenderer()/*createLabelRenderer(null, "\" \" + getKey() + \" -> \" + getValue()", null)*/,
createEnumerationChildrenRenderer(new String[][]{{"key", "getKey()"}, {"value", "getValue()"}})
),
createCompoundReferenceRenderer(
"List", CommonClassNames.JAVA_UTIL_LIST,
createLabelRenderer(" size = ", "size()", null),
new ListChildrenRenderer()
),
createCompoundReferenceRenderer(
"Collection", "java.util.Collection",
createLabelRenderer(" size = ", "size()", null),
createExpressionChildrenRenderer("toArray()", "!isEmpty()")
)
};
@NonNls private static final String HEX_VIEW_ENABLED = "HEX_VIEW_ENABLED";
@NonNls private static final String ALTERNATIVE_COLLECTION_VIEW_ENABLED = "ALTERNATIVE_COLLECTION_VIEW_ENABLED";
@NonNls private static final String CUSTOM_RENDERERS_TAG_NAME = "CustomRenderers";
public NodeRendererSettings() {
myColorRenderer = new ColorObjectRenderer(this);
// default configuration
myHexRenderer.setEnabled(false);
myToStringRenderer.setEnabled(true);
setAlternateCollectionViewsEnabled(true);
myColorRenderer.setEnabled(true);
}
public static NodeRendererSettings getInstance() {
return ServiceManager.getService(NodeRendererSettings.class);
}
/**
* use {@link com.intellij.debugger.ui.tree.render.NodeRenderer} extension
* @param renderer
*/
@Deprecated
public void addPluginRenderer(NodeRenderer renderer) {
myPluginRenderers.add(renderer);
}
@Deprecated
public void removePluginRenderer(NodeRenderer renderer) {
myPluginRenderers.remove(renderer);
}
public void setAlternateCollectionViewsEnabled(boolean enabled) {
for (NodeRenderer myAlternateCollectionRenderer : myAlternateCollectionRenderers) {
myAlternateCollectionRenderer.setEnabled(enabled);
}
}
public boolean areAlternateCollectionViewsEnabled() {
return myAlternateCollectionRenderers[0].isEnabled();
}
public boolean equals(Object o) {
if(!(o instanceof NodeRendererSettings)) return false;
return DebuggerUtilsEx.elementsEqual(getState(), ((NodeRendererSettings)o).getState());
}
public void addListener(NodeRendererSettingsListener listener) {
myDispatcher.addListener(listener);
}
public void removeListener(NodeRendererSettingsListener listener) {
myDispatcher.removeListener(listener);
}
@SuppressWarnings({"HardCodedStringLiteral"})
public Element getState() {
final Element element = new Element("NodeRendererSettings");
JDOMExternalizerUtil.writeField(element, HEX_VIEW_ENABLED, myHexRenderer.isEnabled()? "true" : "false");
JDOMExternalizerUtil.writeField(element, ALTERNATIVE_COLLECTION_VIEW_ENABLED, areAlternateCollectionViewsEnabled()? "true" : "false");
try {
element.addContent(writeRenderer(myArrayRenderer));
element.addContent(writeRenderer(myToStringRenderer));
element.addContent(writeRenderer(myClassRenderer));
if (myCustomRenderers.getRendererCount() > 0) {
final Element custom = new Element(CUSTOM_RENDERERS_TAG_NAME);
element.addContent(custom);
myCustomRenderers.writeExternal(custom);
}
}
catch (WriteExternalException e) {
// ignore
}
return element;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public void loadState(final Element root) {
final String hexEnabled = JDOMExternalizerUtil.readField(root, HEX_VIEW_ENABLED);
if (hexEnabled != null) {
myHexRenderer.setEnabled("true".equalsIgnoreCase(hexEnabled));
}
final String alternativeEnabled = JDOMExternalizerUtil.readField(root, ALTERNATIVE_COLLECTION_VIEW_ENABLED);
if (alternativeEnabled != null) {
setAlternateCollectionViewsEnabled("true".equalsIgnoreCase(alternativeEnabled));
}
final List rendererElements = root.getChildren(RENDERER_TAG);
for (final Object rendererElement : rendererElements) {
final Element elem = (Element)rendererElement;
final String id = elem.getAttributeValue(RENDERER_ID);
if (id == null) {
continue;
}
try {
if (ArrayRenderer.UNIQUE_ID.equals(id)) {
myArrayRenderer.readExternal(elem);
}
else if (ToStringRenderer.UNIQUE_ID.equals(id)) {
myToStringRenderer.readExternal(elem);
}
else if (ClassRenderer.UNIQUE_ID.equals(id)) {
myClassRenderer.readExternal(elem);
}
}
catch (InvalidDataException e) {
// ignore
}
}
final Element custom = root.getChild(CUSTOM_RENDERERS_TAG_NAME);
if (custom != null) {
myCustomRenderers.readExternal(custom);
}
myDispatcher.getMulticaster().renderersChanged();
}
public RendererConfiguration getCustomRenderers() {
return myCustomRenderers;
}
public void setCustomRenderers(@NotNull final RendererConfiguration customRenderers) {
RendererConfiguration oldConfig = myCustomRenderers;
myCustomRenderers = customRenderers;
if (oldConfig == null || !oldConfig.equals(customRenderers)) {
fireRenderersChanged();
}
}
public List<NodeRenderer> getPluginRenderers() {
return new ArrayList<NodeRenderer>(myPluginRenderers);
}
public PrimitiveRenderer getPrimitiveRenderer() {
return myPrimitiveRenderer;
}
public ArrayRenderer getArrayRenderer() {
return myArrayRenderer;
}
public ClassRenderer getClassRenderer() {
return myClassRenderer;
}
public HexRenderer getHexRenderer() {
return myHexRenderer;
}
public ToStringRenderer getToStringRenderer() {
return myToStringRenderer;
}
public NodeRenderer[] getAlternateCollectionRenderers() {
return myAlternateCollectionRenderers;
}
public void fireRenderersChanged() {
myDispatcher.getMulticaster().renderersChanged();
}
public List<NodeRenderer> getAllRenderers() {
// the order is important as the renderers are applied according to it
final List<NodeRenderer> allRenderers = new ArrayList<NodeRenderer>();
allRenderers.add(myHexRenderer);
allRenderers.add(myPrimitiveRenderer);
allRenderers.addAll(myPluginRenderers);
Collections.addAll(allRenderers, NodeRenderer.EP_NAME.getExtensions());
myCustomRenderers.iterateRenderers(new InternalIterator<NodeRenderer>() {
public boolean visit(final NodeRenderer renderer) {
allRenderers.add(renderer);
return true;
}
});
Collections.addAll(allRenderers, myAlternateCollectionRenderers);
allRenderers.add(myColorRenderer);
allRenderers.add(myToStringRenderer);
allRenderers.add(myArrayRenderer);
allRenderers.add(myClassRenderer);
return allRenderers;
}
public boolean isBase(final Renderer renderer) {
return renderer == myPrimitiveRenderer || renderer == myArrayRenderer || renderer == myClassRenderer;
}
public Renderer readRenderer(Element root) throws InvalidDataException {
if (root == null) {
return null;
}
if (!RENDERER_TAG.equals(root.getName())) {
throw new InvalidDataException("Cannot read renderer - tag name is not '" + RENDERER_TAG + "'");
}
final String rendererId = root.getAttributeValue(RENDERER_ID);
if(rendererId == null) {
throw new InvalidDataException("unknown renderer ID: " + rendererId);
}
final Renderer renderer = createRenderer(rendererId);
if(renderer == null) {
throw new InvalidDataException("unknown renderer ID: " + rendererId);
}
renderer.readExternal(root);
return renderer;
}
public Element writeRenderer(Renderer renderer) throws WriteExternalException {
Element root = new Element(RENDERER_TAG);
if(renderer != null) {
root.setAttribute(RENDERER_ID , renderer.getUniqueId());
renderer.writeExternal(root);
}
return root;
}
public Renderer createRenderer(final String rendererId) {
if (ClassRenderer.UNIQUE_ID.equals(rendererId)) {
return myClassRenderer;
}
else if (ArrayRenderer.UNIQUE_ID.equals(rendererId)) {
return myArrayRenderer;
}
else if (PrimitiveRenderer.UNIQUE_ID.equals(rendererId)) {
return myPrimitiveRenderer;
}
else if(HexRenderer.UNIQUE_ID.equals(rendererId)) {
return myHexRenderer;
}
else if(rendererId.equals(ExpressionChildrenRenderer.UNIQUE_ID)) {
return new ExpressionChildrenRenderer();
}
else if(rendererId.equals(LabelRenderer.UNIQUE_ID)) {
return new LabelRenderer();
}
else if(rendererId.equals(EnumerationChildrenRenderer.UNIQUE_ID)) {
return new EnumerationChildrenRenderer();
}
else if(rendererId.equals(ToStringRenderer.UNIQUE_ID)) {
return myToStringRenderer;
}
else if(rendererId.equals(CompoundNodeRenderer.UNIQUE_ID) || rendererId.equals(REFERENCE_RENDERER)) {
return createCompoundReferenceRenderer("unnamed", CommonClassNames.JAVA_LANG_OBJECT, null, null);
}
return null;
}
private CompoundReferenceRenderer createCompoundReferenceRenderer(
@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);
return renderer;
}
private ExpressionChildrenRenderer createExpressionChildrenRenderer(@NonNls String expressionText, @NonNls String childrenExpandableText) {
final ExpressionChildrenRenderer childrenRenderer = new ExpressionChildrenRenderer();
childrenRenderer.setChildrenExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText, "", StdFileTypes.JAVA));
if (childrenExpandableText != null) {
childrenRenderer.setChildrenExpandable(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, childrenExpandableText, "", StdFileTypes.JAVA));
}
return childrenRenderer;
}
private EnumerationChildrenRenderer createEnumerationChildrenRenderer(@NonNls String[][] expressions) {
final EnumerationChildrenRenderer childrenRenderer = new EnumerationChildrenRenderer();
if (expressions != null && expressions.length > 0) {
final ArrayList<Pair<String, TextWithImports>> childrenList = new ArrayList<Pair<String, TextWithImports>>(expressions.length);
for (final String[] expression : expressions) {
childrenList.add(new Pair<String, TextWithImports>(expression[0], new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expression[1], "", StdFileTypes.JAVA)));
}
childrenRenderer.setChildren(childrenList);
}
return childrenRenderer;
}
private static LabelRenderer createLabelRenderer(@NonNls final String prefix, @NonNls final String expressionText, @NonNls final String postfix) {
final LabelRenderer labelRenderer = new LabelRenderer() {
public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener labelListener) throws EvaluateException {
final String evaluated = super.calcLabel(descriptor, evaluationContext, labelListener);
if (prefix == null && postfix == null) {
return evaluated;
}
if (prefix != null && postfix != null) {
return prefix + evaluated + postfix;
}
if (prefix != null) {
return prefix + evaluated;
}
return evaluated + postfix;
}
};
labelRenderer.setLabelExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText, "", StdFileTypes.JAVA));
return labelRenderer;
}
private static class MapEntryLabelRenderer extends ReferenceRenderer implements ValueLabelRenderer{
private static final Computable<String> NULL_LABEL_COMPUTABLE = new Computable<String>() {
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()", "", StdFileTypes.JAVA));
myValueExpression.setReferenceExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, "this.getValue()", "", StdFileTypes.JAVA));
}
public Icon calcValueIcon(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener) throws EvaluateException {
return null;
}
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 ListChildrenRenderer extends ExpressionChildrenRenderer {
private static final ArrayRenderer ourChildrenRenderer = new ArrayRenderer() {
@Override
public PsiExpression getChildValueExpression(DebuggerTreeNode node, DebuggerContext context) {
try {
ArrayElementDescriptorImpl descriptor = (ArrayElementDescriptorImpl)node.getDescriptor();
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(node.getProject()).getElementFactory();
return elementFactory.createExpressionFromText("get(" + descriptor.getIndex() + ")", null);
}
catch (IncorrectOperationException e) {
// fallback to original
return super.getChildValueExpression(node, context);
}
}
};
public ListChildrenRenderer() {
setChildrenExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, "toArray()", "", StdFileTypes.JAVA));
setChildrenExpandable(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, "!isEmpty()", "", StdFileTypes.JAVA));
}
@Override
public void buildChildren(Value value, ChildrenBuilder builder, EvaluationContext evaluationContext) {
if (getLastChildrenRenderer(builder.getParentDescriptor()) == null) {
setPreferableChildrenRenderer(builder.getParentDescriptor(), ourChildrenRenderer);
}
super.buildChildren(value, builder, evaluationContext);
}
}
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) {
StringBuilder sb = new StringBuilder();
sb.append('\"').append(keylabel).append("\" -> ");
if (!StringUtil.isEmpty(valueLabel)) {
sb.append('\"').append(valueLabel).append('\"');
}
return sb.toString();
}
private static String getDescriptorLabel(final ValueDescriptorImpl keyDescriptor) {
return keyDescriptor == null? "null" : keyDescriptor.getValueLabel();
}
}
private static class ColorObjectRenderer extends CompoundReferenceRenderer {
public ColorObjectRenderer(final NodeRendererSettings rendererSettings) {
super(rendererSettings, "Color", null, null);
setClassName("java.awt.Color");
}
public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener) throws EvaluateException {
final ToStringRenderer toStringRenderer = myRendererSettings.getToStringRenderer();
if (toStringRenderer.isEnabled() && DebuggerManagerEx.getInstanceEx(evaluationContext.getProject()).getContext().isEvaluationPossible()) {
return toStringRenderer.calcLabel(descriptor, evaluationContext, listener);
}
return super.calcLabel(descriptor, evaluationContext, listener);
}
public Icon calcValueIcon(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener) throws EvaluateException {
final Value value = descriptor.getValue();
if (value instanceof ObjectReference) {
try {
final ObjectReference objRef = (ObjectReference)value;
final ReferenceType refType = objRef.referenceType();
final Field valueField = refType.fieldByName("value");
if (valueField != null) {
final Value rgbValue = objRef.getValue(valueField);
if (rgbValue instanceof IntegerValue) {
final Color color = new Color(((IntegerValue)rgbValue).value(), true);
return new ColorIcon(16, 12, color, true);
}
}
}
catch (Exception e) {
throw new EvaluateException(e.getMessage(), e);
}
}
return null;
}
}
}