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