3026a3852856911067945edc49bfc5962ed79421
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / JavaValue.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.debugger.engine;
17
18 import com.intellij.debugger.DebuggerBundle;
19 import com.intellij.debugger.SourcePosition;
20 import com.intellij.debugger.actions.JavaReferringObjectsValue;
21 import com.intellij.debugger.actions.JumpToObjectAction;
22 import com.intellij.debugger.engine.evaluation.EvaluateException;
23 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
24 import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
25 import com.intellij.debugger.engine.evaluation.expression.Modifier;
26 import com.intellij.debugger.engine.events.DebuggerCommandImpl;
27 import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
28 import com.intellij.debugger.impl.DebuggerContextImpl;
29 import com.intellij.debugger.impl.DebuggerUtilsEx;
30 import com.intellij.debugger.ui.impl.DebuggerTreeRenderer;
31 import com.intellij.debugger.ui.impl.watch.*;
32 import com.intellij.debugger.ui.tree.*;
33 import com.intellij.debugger.ui.tree.render.*;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.diagnostic.Logger;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.util.Computable;
38 import com.intellij.openapi.util.Ref;
39 import com.intellij.openapi.util.text.StringUtil;
40 import com.intellij.psi.CommonClassNames;
41 import com.intellij.psi.PsiExpression;
42 import com.intellij.psi.util.TypeConversionUtil;
43 import com.intellij.util.ThreeState;
44 import com.intellij.xdebugger.XExpression;
45 import com.intellij.xdebugger.XSourcePosition;
46 import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
47 import com.intellij.xdebugger.evaluation.XInstanceEvaluator;
48 import com.intellij.xdebugger.frame.*;
49 import com.intellij.xdebugger.frame.presentation.XValuePresentation;
50 import com.intellij.xdebugger.impl.evaluate.XValueCompactPresentation;
51 import com.intellij.xdebugger.impl.ui.XValueTextProvider;
52 import com.intellij.xdebugger.impl.ui.tree.XValueExtendedPresentation;
53 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
54 import com.sun.jdi.ArrayReference;
55 import com.sun.jdi.ArrayType;
56 import com.sun.jdi.Value;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59 import org.jetbrains.concurrency.AsyncPromise;
60 import org.jetbrains.concurrency.Promise;
61
62 import javax.swing.*;
63 import java.util.ArrayList;
64 import java.util.List;
65
66 /**
67 * @author egor
68 */
69 public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XValueTextProvider {
70   private static final Logger LOG = Logger.getInstance(JavaValue.class);
71
72   private final JavaValue myParent;
73   private final ValueDescriptorImpl myValueDescriptor;
74   private final EvaluationContextImpl myEvaluationContext;
75   private final NodeManagerImpl myNodeManager;
76   private final boolean myContextSet;
77
78   protected JavaValue(JavaValue parent,
79                     @NotNull ValueDescriptorImpl valueDescriptor,
80                     @NotNull EvaluationContextImpl evaluationContext,
81                     NodeManagerImpl nodeManager,
82                     boolean contextSet) {
83     super(valueDescriptor.getName());
84     myParent = parent;
85     myValueDescriptor = valueDescriptor;
86     myEvaluationContext = evaluationContext;
87     myNodeManager = nodeManager;
88     myContextSet = contextSet;
89   }
90
91   static JavaValue create(JavaValue parent,
92                           @NotNull ValueDescriptorImpl valueDescriptor,
93                           @NotNull EvaluationContextImpl evaluationContext,
94                           NodeManagerImpl nodeManager,
95                           boolean contextSet) {
96     DebuggerManagerThreadImpl.assertIsManagerThread();
97     return new JavaValue(parent, valueDescriptor, evaluationContext, nodeManager, contextSet);
98   }
99
100   static JavaValue create(@NotNull ValueDescriptorImpl valueDescriptor,
101                           @NotNull EvaluationContextImpl evaluationContext,
102                           NodeManagerImpl nodeManager) {
103     return create(null, valueDescriptor, evaluationContext, nodeManager, false);
104   }
105
106   public JavaValue getParent() {
107     return myParent;
108   }
109
110   @Override
111   @NotNull
112   public ValueDescriptorImpl getDescriptor() {
113     return myValueDescriptor;
114   }
115
116   public EvaluationContextImpl getEvaluationContext() {
117     return myEvaluationContext;
118   }
119
120   public NodeManagerImpl getNodeManager() {
121     return myNodeManager;
122   }
123
124   @Override
125   public void computePresentation(@NotNull final XValueNode node, @NotNull XValuePlace place) {
126     final SuspendContextImpl suspendContext = myEvaluationContext.getSuspendContext();
127     myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) {
128       @Override
129       public Priority getPriority() {
130         return Priority.NORMAL;
131       }
132
133       @Override
134       protected void commandCancelled() {
135         node.setPresentation(null, new JavaValuePresentation("", null, DebuggerBundle.message("error.context.has.changed"), myValueDescriptor), false);
136       }
137
138       @Override
139       public void contextAction() throws Exception {
140         if (!myContextSet) {
141           myValueDescriptor.setContext(myEvaluationContext);
142         }
143         myValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() {
144           @Override
145           public void labelChanged() {
146             Icon nodeIcon = DebuggerTreeRenderer.getValueIcon(myValueDescriptor);
147             final String value = getValueString();
148             XValuePresentation presentation;
149             @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
150             EvaluateException exception = myValueDescriptor.getEvaluateException();
151             presentation = new JavaValuePresentation(value, myValueDescriptor.getIdLabel(), exception != null ? exception.getMessage() : null, myValueDescriptor);
152
153             if (myValueDescriptor.getLastRenderer() instanceof FullValueEvaluatorProvider) {
154               XFullValueEvaluator evaluator = ((FullValueEvaluatorProvider)myValueDescriptor.getLastRenderer())
155                 .getFullValueEvaluator(myEvaluationContext, myValueDescriptor);
156               if (evaluator != null) {
157                 node.setFullValueEvaluator(evaluator);
158               }
159             }
160             else if (value.length() > XValueNode.MAX_VALUE_LENGTH) {
161               node.setFullValueEvaluator(new JavaFullValueEvaluator(myEvaluationContext) {
162                 @Override
163                 public void evaluate(@NotNull final XFullValueEvaluationCallback callback) throws Exception {
164                   final ValueDescriptorImpl fullValueDescriptor = myValueDescriptor.getFullValueDescriptor();
165                   fullValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() {
166                     @Override
167                     public void labelChanged() {
168                       callback.evaluated(fullValueDescriptor.getValueText());
169                     }
170                   });
171                 }
172               });
173             }
174             node.setPresentation(nodeIcon, presentation, myValueDescriptor.isExpandable());
175           }
176         });
177       }
178     });
179   }
180
181   public abstract static class JavaFullValueEvaluator extends XFullValueEvaluator {
182     private final EvaluationContextImpl myEvaluationContext;
183
184     public JavaFullValueEvaluator(@NotNull String linkText, EvaluationContextImpl evaluationContext) {
185       super(linkText);
186       myEvaluationContext = evaluationContext;
187     }
188
189     public JavaFullValueEvaluator(EvaluationContextImpl evaluationContext) {
190       myEvaluationContext = evaluationContext;
191     }
192
193     public abstract void evaluate(@NotNull XFullValueEvaluationCallback callback) throws Exception;
194
195     protected EvaluationContextImpl getEvaluationContext() {
196       return myEvaluationContext;
197     }
198
199     @Override
200     public void startEvaluation(@NotNull final XFullValueEvaluationCallback callback) {
201       if (callback.isObsolete()) return;
202       myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
203         @Override
204         public Priority getPriority() {
205           return Priority.NORMAL;
206         }
207
208         @Override
209         protected void commandCancelled() {
210           callback.errorOccurred(DebuggerBundle.message("error.context.has.changed"));
211         }
212
213         @Override
214         public void contextAction() throws Exception {
215           if (callback.isObsolete()) return;
216           evaluate(callback);
217         }
218       });
219     }
220   }
221
222   private static String truncateToMaxLength(String value) {
223     return value.substring(0, Math.min(value.length(), XValueNode.MAX_VALUE_LENGTH));
224   }
225
226   private static class JavaValuePresentation extends XValueExtendedPresentation implements XValueCompactPresentation {
227     private final String myValue;
228     private final String myType;
229     private final String myError;
230     private final ValueDescriptorImpl myValueDescriptor;
231
232     public JavaValuePresentation(@NotNull String value, @Nullable String type, @Nullable String error, ValueDescriptorImpl valueDescriptor) {
233       myValue = value;
234       myType = type;
235       myError = error;
236       myValueDescriptor = valueDescriptor;
237     }
238
239     @Nullable
240     @Override
241     public String getType() {
242       return StringUtil.nullize(myType);
243     }
244
245     @Override
246     public void renderValue(@NotNull XValueTextRenderer renderer) {
247       renderValue(renderer, null);
248     }
249
250     @Override
251     public void renderValue(@NotNull XValueTextRenderer renderer, @Nullable XValueNodeImpl node) {
252       boolean compact = node != null;
253       if (myError != null) {
254         if (myValue.endsWith(myError)) {
255           renderer.renderValue(myValue.substring(0, myValue.length() - myError.length()));
256         }
257         renderer.renderError(myError);
258       }
259       else {
260         if (compact && node.getValueContainer() instanceof JavaValue) {
261           final JavaValue container = (JavaValue)node.getValueContainer();
262
263           if (container.getDescriptor().isArray()) {
264             final ArrayReference value = (ArrayReference)container.getDescriptor().getValue();
265             final ArrayType type = (ArrayType)container.getDescriptor().getType();
266             if (type != null) {
267               final String typeName = type.componentTypeName();
268               if (TypeConversionUtil.isPrimitive(typeName) || CommonClassNames.JAVA_LANG_STRING.equals(typeName)) {
269                 int size = value.length();
270                 int max = Math.min(size, CommonClassNames.JAVA_LANG_STRING.equals(typeName) ? 5 : 10);
271                 //TODO [eu]: this is a quick fix for IDEA-136606, need to move this away from EDT!!!
272                 final List<Value> values = value.getValues(0, max);
273                 int i = 0;
274                 final List<String> vals = new ArrayList<String>(max);
275                 while (i < values.size()) {
276                   vals.add(StringUtil.first(values.get(i).toString(), 15, true));
277                   i++;
278                 }
279                 String more = "";
280                 if (vals.size() < size) {
281                   more = ", + " + (size - vals.size()) + " more";
282                 }
283
284                 renderer.renderValue("{" + StringUtil.join(vals, ", ") + more + "}");
285                 return;
286               }
287             }
288           }
289         }
290
291         String value = myValue;
292         if (myValueDescriptor.isString()) {
293           renderer.renderStringValue(myValue, "\"\\", XValueNode.MAX_VALUE_LENGTH);
294           return;
295         }
296         else if (myValueDescriptor.getLastRenderer() instanceof ToStringRenderer ||
297                  myValueDescriptor.getLastRenderer() instanceof ToStringBasedRenderer) {
298           value = StringUtil.wrapWithDoubleQuote(truncateToMaxLength(myValue));
299         }
300         else if (myValueDescriptor.getLastRenderer() instanceof CompoundReferenceRenderer) {
301           value = truncateToMaxLength(myValue);
302         }
303         renderer.renderValue(value);
304       }
305     }
306
307     @NotNull
308     @Override
309     public String getSeparator() {
310       String fullName = myValueDescriptor.calcValueName();
311       String name = myValueDescriptor.getName();
312       if (!StringUtil.isEmpty(fullName) && !name.equals(fullName) && fullName.startsWith(name)) {
313         return fullName.substring(name.length()) + " " + DEFAULT_SEPARATOR;
314       }
315       return DEFAULT_SEPARATOR;
316     }
317
318     @Override
319     public boolean isModified() {
320       return myValueDescriptor.isDirty();
321     }
322   }
323
324   @NotNull
325   String getValueString() {
326     return myValueDescriptor.getValueText();
327   }
328
329   private int myCurrentChildrenStart = 0;
330
331   @Override
332   public void computeChildren(@NotNull final XCompositeNode node) {
333     scheduleCommand(myEvaluationContext, node, new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
334       @Override
335       public Priority getPriority() {
336         return Priority.NORMAL;
337       }
338
339       @Override
340       public void contextAction() throws Exception {
341         final XValueChildrenList children = new XValueChildrenList();
342         final NodeRenderer renderer = myValueDescriptor.getRenderer(myEvaluationContext.getDebugProcess());
343         final Ref<Integer> remainingNum = new Ref<Integer>(0);
344         renderer.buildChildren(myValueDescriptor.getValue(), new ChildrenBuilder() {
345           @Override
346           public NodeDescriptorFactory getDescriptorManager() {
347             return myNodeManager;
348           }
349
350           @Override
351           public NodeManager getNodeManager() {
352             return myNodeManager;
353           }
354
355           @Override
356           public ValueDescriptor getParentDescriptor() {
357             return myValueDescriptor;
358           }
359
360           @Override
361           public void setRemaining(int remaining) {
362             remainingNum.set(remaining);
363           }
364
365           @Override
366           public void initChildrenArrayRenderer(ArrayRenderer renderer) {
367             renderer.START_INDEX = myCurrentChildrenStart;
368             renderer.END_INDEX = myCurrentChildrenStart + XCompositeNode.MAX_CHILDREN_TO_SHOW - 1;
369             myCurrentChildrenStart += XCompositeNode.MAX_CHILDREN_TO_SHOW;
370           }
371
372           @Override
373           public void setChildren(List<DebuggerTreeNode> nodes) {
374             for (DebuggerTreeNode node : nodes) {
375               final NodeDescriptor descriptor = node.getDescriptor();
376               if (descriptor instanceof ValueDescriptorImpl) {
377                 // Value is calculated already in NodeManagerImpl
378                 children.add(create(JavaValue.this, (ValueDescriptorImpl)descriptor, myEvaluationContext, myNodeManager, false));
379               }
380               else if (descriptor instanceof MessageDescriptor) {
381                 children.add(new JavaStackFrame.DummyMessageValueNode(descriptor.getLabel(), null));
382               }
383             }
384           }
385         }, myEvaluationContext);
386         node.addChildren(children, true);
387         if (remainingNum.get() > 0) {
388           node.tooManyChildren(remainingNum.get());
389         }
390       }
391     });
392   }
393
394   protected static boolean scheduleCommand(EvaluationContextImpl evaluationContext,
395                                         @NotNull final XCompositeNode node,
396                                         final SuspendContextCommandImpl command) {
397     evaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(command.getSuspendContext()) {
398       @Override
399       public void contextAction() throws Exception {
400         command.contextAction();
401       }
402
403       @Override
404       protected void commandCancelled() {
405         node.setErrorMessage(DebuggerBundle.message("error.context.has.changed"));
406       }
407     });
408     return true;
409   }
410
411   @Override
412   public void computeSourcePosition(@NotNull final XNavigatable navigatable) {
413     computeSourcePosition(navigatable, false);
414   }
415
416   private void computeSourcePosition(@NotNull final XNavigatable navigatable, final boolean inline) {
417     myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
418       @Override
419       public Priority getPriority() {
420         return inline ? Priority.LOWEST : Priority.NORMAL;
421       }
422
423       @Override
424       protected void commandCancelled() {
425         navigatable.setSourcePosition(null);
426       }
427
428       @Override
429       public void contextAction() throws Exception {
430         ApplicationManager.getApplication().runReadAction(new Runnable() {
431           @Override
432           public void run() {
433             SourcePosition position = SourcePositionProvider.getSourcePosition(myValueDescriptor, getProject(), getDebuggerContext(), false);
434             if (position != null) {
435               navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
436             }
437             if (inline) {
438               position = SourcePositionProvider.getSourcePosition(myValueDescriptor, getProject(), getDebuggerContext(), true);
439               if (position != null) {
440                 navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
441               }
442             }
443           }
444         });
445       }
446     });
447   }
448
449   @NotNull
450   @Override
451   public ThreeState computeInlineDebuggerData(@NotNull final XInlineDebuggerDataCallback callback) {
452     computeSourcePosition(new XNavigatable() {
453       @Override
454       public void setSourcePosition(@Nullable XSourcePosition sourcePosition) {
455         callback.computed(sourcePosition);
456       }
457     }, true);
458     return ThreeState.YES;
459   }
460
461   private DebuggerContextImpl getDebuggerContext() {
462     return myEvaluationContext.getDebugProcess().getDebuggerContext();
463   }
464
465   public Project getProject() {
466     return myValueDescriptor.getProject();
467   }
468
469   @Override
470   public boolean canNavigateToTypeSource() {
471     return true;
472   }
473
474   @Override
475   public void computeTypeSourcePosition(@NotNull final XNavigatable navigatable) {
476     if (myEvaluationContext.getSuspendContext().isResumed()) return;
477     DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess();
478     debugProcess.getManagerThread().schedule(new JumpToObjectAction.NavigateCommand(getDebuggerContext(), myValueDescriptor, debugProcess, null) {
479       @Override
480       public Priority getPriority() {
481         return Priority.HIGH;
482       }
483
484       @Override
485       protected void doAction(@Nullable final SourcePosition sourcePosition) {
486         if (sourcePosition != null) {
487           ApplicationManager.getApplication().runReadAction(new Runnable() {
488             @Override
489             public void run() {
490               navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(sourcePosition));
491             }
492           });
493         }
494       }
495     });
496   }
497
498   @Nullable
499   @Override
500   public XValueModifier getModifier() {
501     return myValueDescriptor.canSetValue() ? new JavaValueModifier(this) : null;
502   }
503
504   private volatile XExpression evaluationExpression = null;
505
506   @NotNull
507   @Override
508   public Promise<XExpression> calculateEvaluationExpression() {
509     if (evaluationExpression != null) {
510       return Promise.resolve(evaluationExpression);
511     }
512     else {
513       final AsyncPromise<XExpression> res = new AsyncPromise<XExpression>();
514       myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
515         @Override
516         public Priority getPriority() {
517           return Priority.HIGH;
518         }
519
520         @Override
521         public void contextAction() throws Exception {
522           evaluationExpression = ApplicationManager.getApplication().runReadAction(new Computable<XExpression>() {
523             @Override
524             public XExpression compute() {
525               try {
526                 PsiExpression psiExpression = getDescriptor().getTreeEvaluation(JavaValue.this, getDebuggerContext());
527                 if (psiExpression != null) {
528                   return TextWithImportsImpl.toXExpression(new TextWithImportsImpl(psiExpression));
529                 }
530               }
531               catch (EvaluateException e) {
532                 LOG.info(e);
533               }
534               return null;
535             }
536           });
537           res.setResult(evaluationExpression);
538         }
539       });
540       return res;
541     }
542   }
543
544   @Override
545   public String getValueText() {
546     return myValueDescriptor.getValueText();
547   }
548   @Nullable
549   @Override
550   public XReferrersProvider getReferrersProvider() {
551     return new XReferrersProvider() {
552       @Override
553       public XValue getReferringObjectsValue() {
554         return new JavaReferringObjectsValue(JavaValue.this, false);
555       }
556     };
557   }
558
559   @Nullable
560   @Override
561   public XInstanceEvaluator getInstanceEvaluator() {
562     return new XInstanceEvaluator() {
563       @Override
564       public void evaluate(@NotNull final XDebuggerEvaluator.XEvaluationCallback callback, @NotNull final XStackFrame frame) {
565         myEvaluationContext.getManagerThread().schedule(new DebuggerCommandImpl() {
566           @Override
567           protected void commandCancelled() {
568             callback.errorOccurred(DebuggerBundle.message("error.context.has.changed"));
569           }
570
571           @Override
572           protected void action() throws Exception {
573             ValueDescriptorImpl inspectDescriptor = myValueDescriptor;
574             if (myValueDescriptor instanceof WatchItemDescriptor) {
575               Modifier modifier = ((WatchItemDescriptor)myValueDescriptor).getModifier();
576               if (modifier != null) {
577                 NodeDescriptor item = modifier.getInspectItem(getProject());
578                 if (item != null) {
579                   inspectDescriptor = (ValueDescriptorImpl)item;
580                 }
581               }
582             }
583             EvaluationContextImpl evaluationContext = ((JavaStackFrame)frame).getFrameDebuggerContext().createEvaluationContext();
584             if (evaluationContext != null) {
585               callback.evaluated(create(inspectDescriptor, evaluationContext, myNodeManager));
586             }
587             else {
588               callback.errorOccurred("Context is not available");
589             }
590           }
591         });
592       }
593     };
594   }
595
596   public void setRenderer(NodeRenderer nodeRenderer, final XValueNodeImpl node) {
597     DebuggerManagerThreadImpl.assertIsManagerThread();
598     myValueDescriptor.setRenderer(nodeRenderer);
599     reBuild(node);
600   }
601
602   public void reBuild(final XValueNodeImpl node) {
603     DebuggerManagerThreadImpl.assertIsManagerThread();
604     myCurrentChildrenStart = 0;
605     node.getTree().getLaterInvocator().offer(new Runnable() {
606       @Override
607       public void run() {
608         node.clearChildren();
609         computePresentation(node, XValuePlace.TREE);
610       }
611     });
612   }
613 }