EditorConfig documentation test
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / JavaValue.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.debugger.engine;
3
4 import com.intellij.debugger.DebuggerBundle;
5 import com.intellij.debugger.SourcePosition;
6 import com.intellij.debugger.actions.JavaReferringObjectsValue;
7 import com.intellij.debugger.actions.JumpToObjectAction;
8 import com.intellij.debugger.engine.evaluation.EvaluateException;
9 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
10 import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
11 import com.intellij.debugger.engine.evaluation.expression.Modifier;
12 import com.intellij.debugger.engine.events.DebuggerCommandImpl;
13 import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
14 import com.intellij.debugger.impl.DebuggerContextImpl;
15 import com.intellij.debugger.impl.DebuggerUtilsEx;
16 import com.intellij.debugger.memory.agent.MemoryAgent;
17 import com.intellij.debugger.memory.agent.MemoryAgentReferringObjectsProvider;
18 import com.intellij.debugger.ui.impl.DebuggerTreeRenderer;
19 import com.intellij.debugger.ui.impl.watch.*;
20 import com.intellij.debugger.ui.tree.*;
21 import com.intellij.debugger.ui.tree.render.Renderer;
22 import com.intellij.debugger.ui.tree.render.*;
23 import com.intellij.icons.AllIcons;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ReadAction;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.psi.PsiElement;
30 import com.intellij.ui.SimpleTextAttributes;
31 import com.intellij.util.ThreeState;
32 import com.intellij.xdebugger.XExpression;
33 import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
34 import com.intellij.xdebugger.evaluation.XInstanceEvaluator;
35 import com.intellij.xdebugger.frame.*;
36 import com.intellij.xdebugger.frame.presentation.XErrorValuePresentation;
37 import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation;
38 import com.intellij.xdebugger.frame.presentation.XValuePresentation;
39 import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
40 import com.intellij.xdebugger.impl.frame.XValueWithInlinePresentation;
41 import com.intellij.xdebugger.impl.ui.XValueTextProvider;
42 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45 import org.jetbrains.concurrency.AsyncPromise;
46 import org.jetbrains.concurrency.Promise;
47 import org.jetbrains.concurrency.Promises;
48
49 import javax.swing.*;
50 import java.util.List;
51 import java.util.Set;
52
53 /**
54 * @author egor
55 */
56 public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XValueTextProvider, XValueWithInlinePresentation {
57   private static final Logger LOG = Logger.getInstance(JavaValue.class);
58
59   private final JavaValue myParent;
60   private final ValueDescriptorImpl myValueDescriptor;
61   private final EvaluationContextImpl myEvaluationContext;
62   private final NodeManagerImpl myNodeManager;
63   private final boolean myContextSet;
64
65   protected JavaValue(JavaValue parent,
66                     @NotNull ValueDescriptorImpl valueDescriptor,
67                     @NotNull EvaluationContextImpl evaluationContext,
68                     NodeManagerImpl nodeManager,
69                     boolean contextSet) {
70     this(parent, valueDescriptor.calcValueName(), valueDescriptor, evaluationContext, nodeManager, contextSet);
71   }
72
73   protected JavaValue(JavaValue parent,
74                       String name,
75                       @NotNull ValueDescriptorImpl valueDescriptor,
76                       @NotNull EvaluationContextImpl evaluationContext,
77                       NodeManagerImpl nodeManager,
78                       boolean contextSet) {
79     super(name);
80     myParent = parent;
81     myValueDescriptor = valueDescriptor;
82     myEvaluationContext = evaluationContext;
83     myNodeManager = nodeManager;
84     myContextSet = contextSet;
85   }
86
87   public static JavaValue create(JavaValue parent,
88                           @NotNull ValueDescriptorImpl valueDescriptor,
89                           @NotNull EvaluationContextImpl evaluationContext,
90                           NodeManagerImpl nodeManager,
91                           boolean contextSet) {
92     DebuggerManagerThreadImpl.assertIsManagerThread();
93     return new JavaValue(parent, valueDescriptor, evaluationContext, nodeManager, contextSet);
94   }
95
96   static JavaValue create(@NotNull ValueDescriptorImpl valueDescriptor,
97                           @NotNull EvaluationContextImpl evaluationContext,
98                           NodeManagerImpl nodeManager) {
99     return create(null, valueDescriptor, evaluationContext, nodeManager, false);
100   }
101
102   public JavaValue getParent() {
103     return myParent;
104   }
105
106   @Override
107   @NotNull
108   public ValueDescriptorImpl getDescriptor() {
109     return myValueDescriptor;
110   }
111
112   @NotNull
113   public EvaluationContextImpl getEvaluationContext() {
114     return myEvaluationContext;
115   }
116
117   public NodeManagerImpl getNodeManager() {
118     return myNodeManager;
119   }
120
121   private boolean isOnDemand() {
122     return OnDemandRenderer.ON_DEMAND_CALCULATED.isIn(myValueDescriptor);
123   }
124
125   private boolean isCalculated() {
126     return OnDemandRenderer.isCalculated(myValueDescriptor);
127   }
128
129   @Override
130   public void computePresentation(@NotNull final XValueNode node, @NotNull XValuePlace place) {
131     if (isOnDemand() && !isCalculated()) {
132       node.setFullValueEvaluator(OnDemandRenderer.createFullValueEvaluator(DebuggerBundle.message("message.node.evaluate")));
133       node.setPresentation(AllIcons.Debugger.Db_watch, new XRegularValuePresentation("", null, ""), false);
134       return;
135     }
136     myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
137       @Override
138       public Priority getPriority() {
139         return Priority.NORMAL;
140       }
141
142       @Override
143       protected void commandCancelled() {
144         node.setPresentation(null, new XErrorValuePresentation(DebuggerBundle.message("error.context.has.changed")), false);
145       }
146
147       @Override
148       public void contextAction(@NotNull SuspendContextImpl suspendContext) {
149         if (node.isObsolete()) {
150           return;
151         }
152         if (!myContextSet) {
153           myValueDescriptor.setContext(myEvaluationContext);
154         }
155         myValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() {
156           @Override
157           public void labelChanged() {
158             Icon nodeIcon = place == XValuePlace.TOOLTIP
159                             ? myValueDescriptor.getValueIcon()
160                             : DebuggerTreeRenderer.getValueIcon(myValueDescriptor, myParent != null ? myParent.getDescriptor() : null);
161
162             XValuePresentation presentation = createPresentation(myValueDescriptor);
163             Renderer lastRenderer = myValueDescriptor.getLastRenderer();
164             boolean fullEvaluatorSet = setFullValueEvaluator(lastRenderer);
165             if (!fullEvaluatorSet && lastRenderer instanceof CompoundNodeRenderer) {
166               fullEvaluatorSet = setFullValueEvaluator(((CompoundNodeRenderer)lastRenderer).getLabelRenderer());
167             }
168             if (!fullEvaluatorSet && myValueDescriptor.getValueText().length() > XValueNode.MAX_VALUE_LENGTH) {
169               node.setFullValueEvaluator(new JavaFullValueEvaluator(myEvaluationContext) {
170                 @Override
171                 public void evaluate(@NotNull final XFullValueEvaluationCallback callback) {
172                   final ValueDescriptorImpl fullValueDescriptor = myValueDescriptor.getFullValueDescriptor();
173                   fullValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() {
174                     @Override
175                     public void labelChanged() {
176                       callback.evaluated(fullValueDescriptor.getValueText());
177                     }
178                   });
179                 }
180               });
181             }
182             node.setPresentation(nodeIcon, presentation, myValueDescriptor.isExpandable());
183           }
184
185           private boolean setFullValueEvaluator(Renderer renderer) {
186             if (renderer instanceof FullValueEvaluatorProvider) {
187               XFullValueEvaluator evaluator = ((FullValueEvaluatorProvider)renderer).getFullValueEvaluator(myEvaluationContext, myValueDescriptor);
188               if (evaluator != null) {
189                 node.setFullValueEvaluator(evaluator);
190                 return true;
191               }
192             }
193             return false;
194           }
195         });
196       }
197     });
198   }
199
200   public static XValuePresentation createPresentation(ValueDescriptorImpl descriptor) {
201     Renderer lastLabelRenderer = descriptor.getLastLabelRenderer();
202     if (lastLabelRenderer instanceof XValuePresentationProvider) {
203       return ((XValuePresentationProvider)lastLabelRenderer).getPresentation(descriptor);
204     }
205     return new JavaValuePresentation(descriptor);
206   }
207
208   public abstract static class JavaFullValueEvaluator extends XFullValueEvaluator {
209     protected final EvaluationContextImpl myEvaluationContext;
210
211     public JavaFullValueEvaluator(@NotNull String linkText, EvaluationContextImpl evaluationContext) {
212       super(linkText);
213       myEvaluationContext = evaluationContext;
214     }
215
216     public JavaFullValueEvaluator(EvaluationContextImpl evaluationContext) {
217       myEvaluationContext = evaluationContext;
218     }
219
220     public abstract void evaluate(@NotNull XFullValueEvaluationCallback callback) throws Exception;
221
222     protected EvaluationContextImpl getEvaluationContext() {
223       return myEvaluationContext;
224     }
225
226     @Override
227     public void startEvaluation(@NotNull final XFullValueEvaluationCallback callback) {
228       if (callback.isObsolete()) return;
229       myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
230         @Override
231         public Priority getPriority() {
232           return Priority.NORMAL;
233         }
234
235         @Override
236         protected void commandCancelled() {
237           callback.errorOccurred(DebuggerBundle.message("error.context.has.changed"));
238         }
239
240         @Override
241         public void contextAction(@NotNull SuspendContextImpl suspendContext) throws Exception {
242           if (callback.isObsolete()) return;
243           evaluate(callback);
244         }
245       });
246     }
247   }
248
249   private int myChildrenRemaining = -1;
250
251   @Override
252   public void computeChildren(@NotNull final XCompositeNode node) {
253     scheduleCommand(myEvaluationContext, node, new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
254       @Override
255       public Priority getPriority() {
256         return Priority.NORMAL;
257       }
258
259       @Override
260       public void contextAction(@NotNull SuspendContextImpl suspendContext) {
261         myValueDescriptor.getChildrenRenderer(myEvaluationContext.getDebugProcess())
262           .buildChildren(myValueDescriptor.getValue(), new ChildrenBuilder() {
263           @Override
264           public NodeDescriptorFactory getDescriptorManager() {
265             return myNodeManager;
266           }
267
268           @Override
269           public NodeManager getNodeManager() {
270             return myNodeManager;
271           }
272
273           @Override
274           public ValueDescriptor getParentDescriptor() {
275             return myValueDescriptor;
276           }
277
278           @Override
279           public void initChildrenArrayRenderer(ArrayRenderer renderer, int arrayLength) {
280             renderer.START_INDEX = 0;
281             if (myChildrenRemaining >= 0) {
282               renderer.START_INDEX = Math.max(0, arrayLength - myChildrenRemaining);
283             }
284           }
285
286           @Override
287           public void addChildren(List<? extends DebuggerTreeNode> nodes, boolean last) {
288             XValueChildrenList childrenList = XValueChildrenList.EMPTY;
289             if (!nodes.isEmpty()) {
290               childrenList = new XValueChildrenList(nodes.size());
291               for (DebuggerTreeNode treeNode : nodes) {
292                 NodeDescriptor descriptor = treeNode.getDescriptor();
293                 if (descriptor instanceof ValueDescriptorImpl) {
294                   // Value is calculated already in NodeManagerImpl
295                   childrenList.add(create(JavaValue.this, (ValueDescriptorImpl)descriptor, myEvaluationContext, myNodeManager, false));
296                 }
297                 else if (descriptor instanceof MessageDescriptor) {
298                   childrenList.add(
299                     new JavaStackFrame.DummyMessageValueNode(descriptor.getLabel(), DebuggerTreeRenderer.getDescriptorIcon(descriptor)));
300                 }
301               }
302             }
303             node.addChildren(childrenList, last);
304           }
305
306           @Override
307           public void setChildren(List<? extends DebuggerTreeNode> nodes) {
308             addChildren(nodes, true);
309           }
310
311           @Override
312           public void setMessage(@NotNull String message,
313                                  @Nullable Icon icon,
314                                  @NotNull SimpleTextAttributes attributes,
315                                  @Nullable XDebuggerTreeNodeHyperlink link) {
316             node.setMessage(message, icon, attributes, link);
317           }
318
319           @Override
320           public void addChildren(@NotNull XValueChildrenList children, boolean last) {
321             node.addChildren(children, last);
322           }
323
324           @Override
325           public void tooManyChildren(int remaining) {
326             myChildrenRemaining = remaining;
327             node.tooManyChildren(remaining);
328           }
329
330           @Override
331           public void setAlreadySorted(boolean alreadySorted) {
332             node.setAlreadySorted(alreadySorted);
333           }
334
335           @Override
336           public void setErrorMessage(@NotNull String errorMessage) {
337             node.setErrorMessage(errorMessage);
338           }
339
340           @Override
341           public void setErrorMessage(@NotNull String errorMessage, @Nullable XDebuggerTreeNodeHyperlink link) {
342             node.setErrorMessage(errorMessage, link);
343           }
344
345           @Override
346           public boolean isObsolete() {
347             return node.isObsolete();
348           }
349         }, myEvaluationContext);
350       }
351     });
352   }
353
354   protected static boolean scheduleCommand(EvaluationContextImpl evaluationContext,
355                                         @NotNull final XCompositeNode node,
356                                         final SuspendContextCommandImpl command) {
357     if (node.isObsolete()) {
358       return false;
359     }
360     evaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(command.getSuspendContext()) {
361       @Override
362       public void contextAction(@NotNull SuspendContextImpl suspendContext) throws Exception {
363         if (node.isObsolete()) {
364           return;
365         }
366         command.contextAction(suspendContext);
367       }
368
369       @Override
370       protected void commandCancelled() {
371         node.setErrorMessage(DebuggerBundle.message("error.context.has.changed"));
372       }
373     });
374     return true;
375   }
376
377   @Override
378   public void computeSourcePosition(@NotNull final XNavigatable navigatable) {
379     computeSourcePosition(navigatable, false);
380   }
381
382   private void computeSourcePosition(@NotNull final XNavigatable navigatable, final boolean inline) {
383     myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
384       @Override
385       public Priority getPriority() {
386         return inline ? Priority.LOWEST : Priority.NORMAL;
387       }
388
389       @Override
390       protected void commandCancelled() {
391         navigatable.setSourcePosition(null);
392       }
393
394       @Override
395       public void contextAction(@NotNull SuspendContextImpl suspendContext) {
396         ApplicationManager.getApplication().runReadAction(() -> {
397           SourcePosition position = SourcePositionProvider.getSourcePosition(myValueDescriptor, getProject(), getDebuggerContext(), false);
398           if (position != null) {
399             navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
400           }
401           if (inline) {
402             position = SourcePositionProvider.getSourcePosition(myValueDescriptor, getProject(), getDebuggerContext(), true);
403             if (position != null) {
404               navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
405             }
406           }
407         });
408       }
409     });
410   }
411
412   @NotNull
413   @Override
414   public ThreeState computeInlineDebuggerData(@NotNull final XInlineDebuggerDataCallback callback) {
415     computeSourcePosition(callback::computed, true);
416     return ThreeState.YES;
417   }
418
419   private DebuggerContextImpl getDebuggerContext() {
420     return myEvaluationContext.getDebugProcess().getDebuggerContext();
421   }
422
423   public Project getProject() {
424     return myValueDescriptor.getProject();
425   }
426
427   @Override
428   public boolean canNavigateToTypeSource() {
429     return true;
430   }
431
432   @Override
433   public void computeTypeSourcePosition(@NotNull final XNavigatable navigatable) {
434     if (myEvaluationContext.getSuspendContext().isResumed()) return;
435     DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess();
436     debugProcess.getManagerThread().schedule(new JumpToObjectAction.NavigateCommand(getDebuggerContext(), myValueDescriptor, debugProcess, null) {
437       @Override
438       public Priority getPriority() {
439         return Priority.HIGH;
440       }
441
442       @Override
443       protected void doAction(@Nullable final SourcePosition sourcePosition) {
444         if (sourcePosition != null) {
445           ApplicationManager.getApplication().runReadAction(() -> navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(sourcePosition)));
446         }
447       }
448     });
449   }
450
451   @Nullable
452   @Override
453   public XValueModifier getModifier() {
454     return myValueDescriptor.canSetValue() ? myValueDescriptor.getModifier(this) : null;
455   }
456
457   private volatile XExpression evaluationExpression = null;
458
459   @NotNull
460   @Override
461   public Promise<XExpression> calculateEvaluationExpression() {
462     if (evaluationExpression != null) {
463       return Promises.resolvedPromise(evaluationExpression);
464     }
465     else {
466       final AsyncPromise<XExpression> res = new AsyncPromise<>();
467       myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
468         @Override
469         public Priority getPriority() {
470           return Priority.HIGH;
471         }
472
473         @Override
474         public void contextAction(@NotNull SuspendContextImpl suspendContext) {
475           evaluationExpression = ReadAction.compute(() -> {
476             try {
477               PsiElement psiExpression = getDescriptor().getTreeEvaluation(JavaValue.this, getDebuggerContext());
478               if (psiExpression != null) {
479                 XExpression res = TextWithImportsImpl.toXExpression(new TextWithImportsImpl(psiExpression));
480                 // add runtime imports if any
481                 Set<String> imports = psiExpression.getUserData(DebuggerTreeNodeExpression.ADDITIONAL_IMPORTS_KEY);
482                 if (imports != null && res != null) {
483                   if (res.getCustomInfo() != null) {
484                     imports.add(res.getCustomInfo());
485                   }
486                   res = new XExpressionImpl(res.getExpression(), res.getLanguage(), StringUtil.join(imports, ","), res.getMode());
487                 }
488                 return res;
489               }
490             }
491             catch (EvaluateException e) {
492               LOG.info(e);
493             }
494             return null;
495           });
496           res.setResult(evaluationExpression);
497         }
498       });
499       return res;
500     }
501   }
502
503   @Override
504   @Nullable
505   public String getValueText() {
506     if (myValueDescriptor.getLastLabelRenderer() instanceof XValuePresentationProvider) {
507       return null;
508     }
509     return myValueDescriptor.getValueText();
510   }
511
512   @Nullable
513   @Override
514   public XReferrersProvider getReferrersProvider() {
515     return new XReferrersProvider() {
516       @Override
517       public XValue getReferringObjectsValue() {
518         ReferringObjectsProvider provider = ReferringObjectsProvider.BASIC_JDI;
519
520         if (MemoryAgent.get(getEvaluationContext().getDebugProcess()).capabilities().canGetReferringObjects()) {
521           provider = new MemoryAgentReferringObjectsProvider(MemoryAgent.DEFAULT_GC_ROOTS_OBJECTS_LIMIT);
522         }
523         return new JavaReferringObjectsValue(JavaValue.this, provider, null);
524       }
525     };
526   }
527
528   @Nullable
529   @Override
530   public XInstanceEvaluator getInstanceEvaluator() {
531     return new XInstanceEvaluator() {
532       @Override
533       public void evaluate(@NotNull final XDebuggerEvaluator.XEvaluationCallback callback, @NotNull final XStackFrame frame) {
534         myEvaluationContext.getManagerThread().schedule(new DebuggerCommandImpl() {
535           @Override
536           protected void commandCancelled() {
537             callback.errorOccurred(DebuggerBundle.message("error.context.has.changed"));
538           }
539
540           @Override
541           protected void action() {
542             ValueDescriptorImpl inspectDescriptor = myValueDescriptor;
543             if (myValueDescriptor instanceof WatchItemDescriptor) {
544               Modifier modifier = ((WatchItemDescriptor)myValueDescriptor).getModifier();
545               if (modifier != null) {
546                 NodeDescriptor item = modifier.getInspectItem(getProject());
547                 if (item != null) {
548                   inspectDescriptor = (ValueDescriptorImpl)item;
549                 }
550               }
551             }
552             EvaluationContextImpl evaluationContext = ((JavaStackFrame)frame).getFrameDebuggerContext(null).createEvaluationContext();
553             if (evaluationContext != null) {
554               callback.evaluated(create(inspectDescriptor, evaluationContext, myNodeManager));
555             }
556             else {
557               callback.errorOccurred("Context is not available");
558             }
559           }
560         });
561       }
562     };
563   }
564
565   public void setRenderer(NodeRenderer nodeRenderer, final XValueNodeImpl node) {
566     DebuggerManagerThreadImpl.assertIsManagerThread();
567     myValueDescriptor.setRenderer(nodeRenderer);
568     reBuild(node);
569   }
570
571   public void reBuild(final XValueNodeImpl node) {
572     DebuggerManagerThreadImpl.assertIsManagerThread();
573     myChildrenRemaining = -1;
574     node.invokeNodeUpdate(() -> {
575       node.clearChildren();
576       computePresentation(node, XValuePlace.TREE);
577     });
578   }
579
580   @Nullable
581   @Override
582   public String computeInlinePresentation() {
583     ValueDescriptorImpl descriptor = getDescriptor();
584     return descriptor.isNull() || descriptor.isPrimitive() ? descriptor.getValueText() : null;
585   }
586 }