| /* |
| * Copyright 2000-2014 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.engine; |
| |
| import com.intellij.debugger.DebuggerBundle; |
| import com.intellij.debugger.DebuggerInvocationUtil; |
| import com.intellij.debugger.SourcePosition; |
| import com.intellij.debugger.actions.JavaReferringObjectsValue; |
| import com.intellij.debugger.actions.JumpToObjectAction; |
| import com.intellij.debugger.engine.evaluation.EvaluateException; |
| import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; |
| import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; |
| import com.intellij.debugger.engine.events.DebuggerCommandImpl; |
| import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; |
| import com.intellij.debugger.engine.events.SuspendContextCommandImpl; |
| import com.intellij.debugger.impl.DebuggerContextImpl; |
| import com.intellij.debugger.impl.DebuggerUtilsEx; |
| import com.intellij.debugger.ui.impl.DebuggerTreeRenderer; |
| import com.intellij.debugger.ui.impl.watch.*; |
| import com.intellij.debugger.ui.tree.*; |
| import com.intellij.debugger.ui.tree.render.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.CommonClassNames; |
| import com.intellij.psi.PsiExpression; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.xdebugger.frame.*; |
| import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation; |
| import com.intellij.xdebugger.frame.presentation.XStringValuePresentation; |
| import com.intellij.xdebugger.frame.presentation.XValuePresentation; |
| import com.intellij.xdebugger.impl.evaluate.XValueCompactPresentation; |
| import com.intellij.xdebugger.impl.ui.XValueTextProvider; |
| import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; |
| import com.sun.jdi.ArrayReference; |
| import com.sun.jdi.ArrayType; |
| import com.sun.jdi.Value; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * @author egor |
| */ |
| public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XValueTextProvider { |
| private static final Logger LOG = Logger.getInstance(JavaValue.class); |
| |
| private final JavaValue myParent; |
| private final ValueDescriptorImpl myValueDescriptor; |
| private final EvaluationContextImpl myEvaluationContext; |
| private final NodeManagerImpl myNodeManager; |
| private final boolean myContextSet; |
| |
| protected JavaValue(JavaValue parent, |
| @NotNull ValueDescriptorImpl valueDescriptor, |
| @NotNull EvaluationContextImpl evaluationContext, |
| NodeManagerImpl nodeManager, |
| boolean contextSet) { |
| super(valueDescriptor.getName()); |
| myParent = parent; |
| myValueDescriptor = valueDescriptor; |
| myEvaluationContext = evaluationContext; |
| myNodeManager = nodeManager; |
| myContextSet = contextSet; |
| } |
| |
| static JavaValue create(JavaValue parent, |
| @NotNull ValueDescriptorImpl valueDescriptor, |
| EvaluationContextImpl evaluationContext, |
| NodeManagerImpl nodeManager, |
| boolean contextSet) { |
| DebuggerManagerThreadImpl.assertIsManagerThread(); |
| return new JavaValue(parent, valueDescriptor, evaluationContext, nodeManager, contextSet); |
| } |
| |
| static JavaValue create(@NotNull ValueDescriptorImpl valueDescriptor, |
| EvaluationContextImpl evaluationContext, |
| NodeManagerImpl nodeManager) { |
| return create(null, valueDescriptor, evaluationContext, nodeManager, false); |
| } |
| |
| public JavaValue getParent() { |
| return myParent; |
| } |
| |
| @Override |
| @NotNull |
| public ValueDescriptorImpl getDescriptor() { |
| return myValueDescriptor; |
| } |
| |
| public EvaluationContextImpl getEvaluationContext() { |
| return myEvaluationContext; |
| } |
| |
| @Override |
| public void computePresentation(@NotNull final XValueNode node, @NotNull XValuePlace place) { |
| if (myEvaluationContext.getSuspendContext().isResumed()) return; |
| myEvaluationContext.getDebugProcess().getManagerThread().schedule(new DebuggerContextCommandImpl(getDebuggerContext()) { |
| @Override |
| public Priority getPriority() { |
| return Priority.NORMAL; |
| } |
| |
| @Override |
| public void threadAction() { |
| if (!myContextSet) { |
| myValueDescriptor.setContext(myEvaluationContext); |
| } |
| myValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() { |
| @Override |
| public void labelChanged() { |
| Icon nodeIcon = DebuggerTreeRenderer.getValueIcon(myValueDescriptor); |
| final String[] strings = splitValue(myValueDescriptor.getValueLabel()); |
| final String value = StringUtil.notNullize(strings[1]); |
| String type = strings[0]; |
| XValuePresentation presentation; |
| if (myValueDescriptor.isString()) { |
| presentation = new TypedStringValuePresentation(value, type); |
| } |
| else { |
| EvaluateException exception = myValueDescriptor.getEvaluateException(); |
| if (myValueDescriptor.getLastRenderer() instanceof ToStringRenderer && exception == null) { |
| presentation = new XRegularValuePresentation(StringUtil.wrapWithDoubleQuote(value.substring(0,Math.min(value.length(), XValueNode.MAX_VALUE_LENGTH))), type); |
| } |
| else { |
| presentation = new JavaValuePresentation(value, type, exception != null ? exception.getMessage() : null); |
| } |
| } |
| if (value.length() > XValueNode.MAX_VALUE_LENGTH) { |
| node.setFullValueEvaluator(new XFullValueEvaluator() { |
| @Override |
| public void startEvaluation(@NotNull final XFullValueEvaluationCallback callback) { |
| myEvaluationContext.getDebugProcess().getManagerThread().schedule(new DebuggerContextCommandImpl(getDebuggerContext()) { |
| @Override |
| public Priority getPriority() { |
| return Priority.NORMAL; |
| } |
| |
| @Override |
| public void threadAction() { |
| final String valueAsString = DebuggerUtilsEx.getValueOrErrorAsString(myEvaluationContext, myValueDescriptor.getValue()); |
| DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() { |
| @Override |
| public void run() { |
| callback.evaluated(valueAsString); |
| } |
| }); |
| } |
| }); |
| } |
| }); |
| } |
| node.setPresentation(nodeIcon, presentation, myValueDescriptor.isExpandable()); |
| } |
| }); |
| } |
| }); |
| } |
| |
| private static class JavaValuePresentation extends XValuePresentation implements XValueCompactPresentation { |
| private final String myValue; |
| private final String myType; |
| private final String myError; |
| |
| public JavaValuePresentation(@NotNull String value, @Nullable String type, @Nullable String error) { |
| myValue = value; |
| myType = type; |
| myError = error; |
| } |
| |
| @Nullable |
| @Override |
| public String getType() { |
| return myType; |
| } |
| |
| @Override |
| public void renderValue(@NotNull XValueTextRenderer renderer) { |
| renderValue(renderer, null); |
| } |
| |
| @Override |
| public void renderValue(@NotNull XValueTextRenderer renderer, @Nullable XValueNodeImpl node) { |
| boolean compact = node != null; |
| if (myError != null) { |
| if (myValue.endsWith(myError)) { |
| renderer.renderValue(myValue.substring(0, myValue.length() - myError.length())); |
| } |
| renderer.renderError(myError); |
| } |
| else { |
| if (compact && node.getValueContainer() instanceof JavaValue) { |
| final JavaValue container = (JavaValue)node.getValueContainer(); |
| |
| if (container.getDescriptor().isArray()) { |
| final ArrayReference value = (ArrayReference)container.getDescriptor().getValue(); |
| final ArrayType type = (ArrayType)container.getDescriptor().getType(); |
| if (type != null) { |
| final String typeName = type.componentTypeName(); |
| if (TypeConversionUtil.isPrimitive(typeName) || CommonClassNames.JAVA_LANG_STRING.equals(typeName)) { |
| int max = CommonClassNames.JAVA_LANG_STRING.equals(typeName) ? 5 : 10; |
| final List<Value> values = value.getValues(); |
| int i = 0; |
| final List<String> vals = new ArrayList<String>(max); |
| while (i < values.size() && i <= max) { |
| vals.add(StringUtil.first(values.get(i).toString(), 15, true)); |
| i++; |
| } |
| String more = ""; |
| if (vals.size() < values.size()) { |
| more = ", + " + (values.size() - vals.size()) + " more"; |
| } |
| |
| renderer.renderValue("{" + StringUtil.join(vals, ", ") + more + "}"); |
| return; |
| } |
| } |
| } |
| } |
| renderer.renderValue(myValue); |
| } |
| } |
| } |
| |
| String getValueString() { |
| return splitValue(myValueDescriptor.getValueLabel())[1]; |
| } |
| |
| private static class TypedStringValuePresentation extends XStringValuePresentation { |
| private final String myType; |
| |
| public TypedStringValuePresentation(@NotNull String value, @Nullable String type) { |
| super(value); |
| myType = type; |
| } |
| |
| @Nullable |
| @Override |
| public String getType() { |
| return myType; |
| } |
| } |
| |
| private static String[] splitValue(String value) { |
| if (StringUtil.startsWithChar(value, '{')) { |
| int end = value.indexOf('}'); |
| if (end > 0) { |
| return new String[]{value.substring(1, end), value.substring(end+1)}; |
| } |
| } |
| return new String[]{null, value}; |
| } |
| |
| private int currentStart = 0; |
| |
| @Override |
| public void computeChildren(@NotNull final XCompositeNode node) { |
| if (checkContextNotResumed(node)) return; |
| myEvaluationContext.getDebugProcess().getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) { |
| @Override |
| public Priority getPriority() { |
| return Priority.NORMAL; |
| } |
| |
| @Override |
| public void contextAction() throws Exception { |
| final XValueChildrenList children = new XValueChildrenList(); |
| final NodeRenderer renderer = myValueDescriptor.getRenderer(myEvaluationContext.getDebugProcess()); |
| final Ref<Integer> remainingNum = new Ref<Integer>(0); |
| renderer.buildChildren(myValueDescriptor.getValue(), new ChildrenBuilder() { |
| @Override |
| public NodeDescriptorFactory getDescriptorManager() { |
| return myNodeManager; |
| } |
| |
| @Override |
| public NodeManager getNodeManager() { |
| return myNodeManager; |
| } |
| |
| @Override |
| public ValueDescriptor getParentDescriptor() { |
| return myValueDescriptor; |
| } |
| |
| @Override |
| public void setRemaining(int remaining) { |
| remainingNum.set(remaining); |
| } |
| |
| @Override |
| public void initChildrenArrayRenderer(ArrayRenderer renderer) { |
| renderer.START_INDEX = currentStart; |
| renderer.END_INDEX = currentStart + XCompositeNode.MAX_CHILDREN_TO_SHOW - 1; |
| currentStart += XCompositeNode.MAX_CHILDREN_TO_SHOW; |
| } |
| |
| @Override |
| public void setChildren(List<DebuggerTreeNode> nodes) { |
| for (DebuggerTreeNode node : nodes) { |
| final NodeDescriptor descriptor = node.getDescriptor(); |
| if (descriptor instanceof ValueDescriptorImpl) { |
| // Value is calculated already in NodeManagerImpl |
| children.add(create(JavaValue.this, (ValueDescriptorImpl)descriptor, myEvaluationContext, myNodeManager, false)); |
| } |
| else if (descriptor instanceof MessageDescriptor) { |
| children.add(new JavaStackFrame.DummyMessageValueNode(descriptor.getLabel(), null)); |
| } |
| } |
| } |
| }, myEvaluationContext); |
| node.addChildren(children, true); |
| if (remainingNum.get() > 0) { |
| node.tooManyChildren(remainingNum.get()); |
| } |
| } |
| }); |
| } |
| |
| protected boolean checkContextNotResumed(XCompositeNode node) { |
| if (myEvaluationContext.getSuspendContext().isResumed()) { |
| node.setErrorMessage(DebuggerBundle.message("error.context.has.changed")); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void computeSourcePosition(@NotNull final XNavigatable navigatable) { |
| if (myEvaluationContext.getSuspendContext().isResumed()) return; |
| myEvaluationContext.getDebugProcess().getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) { |
| @Override |
| public Priority getPriority() { |
| return Priority.NORMAL; |
| } |
| |
| @Override |
| public void contextAction() throws Exception { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final boolean nearest = navigatable instanceof XNearestSourcePosition; |
| if (myValueDescriptor instanceof FieldDescriptorImpl) { |
| SourcePosition position = ((FieldDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext(), nearest); |
| if (position != null) { |
| navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position)); |
| } |
| } |
| if (myValueDescriptor instanceof LocalVariableDescriptorImpl) { |
| SourcePosition position = |
| ((LocalVariableDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext(), nearest); |
| if (position != null) { |
| navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position)); |
| } |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| private DebuggerContextImpl getDebuggerContext() { |
| return myEvaluationContext.getDebugProcess().getDebuggerContext(); |
| } |
| |
| public Project getProject() { |
| return myEvaluationContext.getProject(); |
| } |
| |
| @Override |
| public boolean canNavigateToTypeSource() { |
| return true; |
| } |
| |
| @Override |
| public void computeTypeSourcePosition(@NotNull final XNavigatable navigatable) { |
| if (myEvaluationContext.getSuspendContext().isResumed()) return; |
| DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess(); |
| debugProcess.getManagerThread().schedule(new JumpToObjectAction.NavigateCommand(getDebuggerContext(), myValueDescriptor, debugProcess, null) { |
| @Override |
| public Priority getPriority() { |
| return Priority.HIGH; |
| } |
| |
| @Override |
| protected void doAction(@Nullable final SourcePosition sourcePosition) { |
| if (sourcePosition != null) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(sourcePosition)); |
| } |
| }); |
| } |
| } |
| }); |
| } |
| |
| @Nullable |
| @Override |
| public XValueModifier getModifier() { |
| return myValueDescriptor.canSetValue() ? new JavaValueModifier(this) : null; |
| } |
| |
| |
| private volatile String evaluationExpression = null; |
| @Nullable |
| @Override |
| public String getEvaluationExpression() { |
| if (evaluationExpression == null) { |
| // TODO: change API to allow to calculate it asynchronously |
| if (myEvaluationContext.getSuspendContext().isResumed()) return null; |
| DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess(); |
| debugProcess.getManagerThread().invokeAndWait(new DebuggerCommandImpl() { |
| @Override |
| public Priority getPriority() { |
| return Priority.HIGH; |
| } |
| |
| @Override |
| protected void action() throws Exception { |
| evaluationExpression = ApplicationManager.getApplication().runReadAction(new Computable<String>() { |
| @Override |
| public String compute() { |
| try { |
| PsiExpression psiExpression = getDescriptor().getTreeEvaluation(JavaValue.this, getDebuggerContext()); |
| if (psiExpression != null) { |
| return new TextWithImportsImpl(psiExpression).getText(); |
| } |
| } |
| catch (EvaluateException e) { |
| LOG.info(e); |
| } |
| return null; |
| } |
| }); |
| } |
| }); |
| } |
| return evaluationExpression; |
| } |
| |
| @Override |
| public String getValueText() { |
| return myValueDescriptor.getValueText(); |
| } |
| @Nullable |
| @Override |
| public XReferrersProvider getReferrersProvider() { |
| return new XReferrersProvider() { |
| @Override |
| public XValue getReferringObjectsValue() { |
| return new JavaReferringObjectsValue(JavaValue.this, false); |
| } |
| }; |
| } |
| } |