IDEA-131931 Debugger: Throwable at SuspendContextImpl.getDebugProcess() on opening...
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / JavaValue.java
1 /*
2  * Copyright 2000-2014 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.DebuggerInvocationUtil;
20 import com.intellij.debugger.SourcePosition;
21 import com.intellij.debugger.actions.JavaReferringObjectsValue;
22 import com.intellij.debugger.actions.JumpToObjectAction;
23 import com.intellij.debugger.engine.evaluation.EvaluateException;
24 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
25 import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
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.xdebugger.evaluation.XDebuggerEvaluator;
44 import com.intellij.xdebugger.evaluation.XInstanceEvaluator;
45 import com.intellij.xdebugger.frame.*;
46 import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation;
47 import com.intellij.xdebugger.frame.presentation.XStringValuePresentation;
48 import com.intellij.xdebugger.frame.presentation.XValuePresentation;
49 import com.intellij.xdebugger.impl.evaluate.XValueCompactPresentation;
50 import com.intellij.xdebugger.impl.ui.XValueTextProvider;
51 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
52 import com.sun.jdi.ArrayReference;
53 import com.sun.jdi.ArrayType;
54 import com.sun.jdi.Value;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import java.util.ArrayList;
60 import java.util.List;
61
62 /**
63 * @author egor
64 */
65 public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XValueTextProvider {
66   private static final Logger LOG = Logger.getInstance(JavaValue.class);
67
68   private final JavaValue myParent;
69   private final ValueDescriptorImpl myValueDescriptor;
70   private final EvaluationContextImpl myEvaluationContext;
71   private final NodeManagerImpl myNodeManager;
72   private final boolean myContextSet;
73
74   protected JavaValue(JavaValue parent,
75                     @NotNull ValueDescriptorImpl valueDescriptor,
76                     @NotNull EvaluationContextImpl evaluationContext,
77                     NodeManagerImpl nodeManager,
78                     boolean contextSet) {
79     super(valueDescriptor.getName());
80     myParent = parent;
81     myValueDescriptor = valueDescriptor;
82     myEvaluationContext = evaluationContext;
83     myNodeManager = nodeManager;
84     myContextSet = contextSet;
85   }
86
87   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   public EvaluationContextImpl getEvaluationContext() {
113     return myEvaluationContext;
114   }
115
116   public NodeManagerImpl getNodeManager() {
117     return myNodeManager;
118   }
119
120   @Override
121   public void computePresentation(@NotNull final XValueNode node, @NotNull XValuePlace place) {
122     final SuspendContextImpl suspendContext = myEvaluationContext.getSuspendContext();
123     myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) {
124       @Override
125       public Priority getPriority() {
126         return Priority.NORMAL;
127       }
128
129       @Override
130       protected void commandCancelled() {
131         node.setPresentation(null, new JavaValuePresentation("", null, DebuggerBundle.message("error.context.has.changed")), false);
132       }
133
134       @Override
135       public void contextAction() throws Exception {
136         if (!myContextSet) {
137           myValueDescriptor.setContext(myEvaluationContext);
138         }
139         myValueDescriptor.updateRepresentation(myEvaluationContext, new DescriptorLabelListener() {
140           @Override
141           public void labelChanged() {
142             Icon nodeIcon = DebuggerTreeRenderer.getValueIcon(myValueDescriptor);
143             final String[] strings = splitValue(myValueDescriptor.getValueLabel());
144             final String value = StringUtil.notNullize(strings[1]);
145             String type = strings[0];
146             XValuePresentation presentation;
147             if (myValueDescriptor.isString()) {
148               presentation = new TypedStringValuePresentation(value, type);
149             }
150             else {
151               @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
152               EvaluateException exception = myValueDescriptor.getEvaluateException();
153               if (myValueDescriptor.getLastRenderer() instanceof ToStringRenderer && exception == null) {
154                 presentation = new XRegularValuePresentation(StringUtil.wrapWithDoubleQuote(truncateToMaxLength(value)), type);
155               }
156               else if (myValueDescriptor.getLastRenderer() instanceof CompoundReferenceRenderer && exception == null) {
157                 presentation = new XRegularValuePresentation(truncateToMaxLength(value), type);
158               }
159               else {
160                 presentation = new JavaValuePresentation(value, type, exception != null ? exception.getMessage() : null);
161               }
162             }
163             if (myValueDescriptor.getLastRenderer() instanceof FullValueEvaluatorProvider) {
164               node.setFullValueEvaluator(((FullValueEvaluatorProvider)myValueDescriptor.getLastRenderer()).getFullValueEvaluator(myEvaluationContext, myValueDescriptor));
165             }
166             else if (value.length() > XValueNode.MAX_VALUE_LENGTH) {
167               node.setFullValueEvaluator(new XFullValueEvaluator() {
168                 @Override
169                 public void startEvaluation(@NotNull final XFullValueEvaluationCallback callback) {
170                   myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) {
171                     @Override
172                     public Priority getPriority() {
173                       return Priority.NORMAL;
174                     }
175
176                     @Override
177                     protected void commandCancelled() {
178                       callback.errorOccurred(DebuggerBundle.message("error.context.has.changed"));
179                     }
180
181                     @Override
182                     public void contextAction() throws Exception {
183                       final String valueAsString = myValueDescriptor.getValueText();
184                       DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() {
185                         @Override
186                         public void run() {
187                           callback.evaluated(valueAsString);
188                         }
189                       });
190                     }
191                   });
192                 }
193               });
194             }
195             node.setPresentation(nodeIcon, presentation, myValueDescriptor.isExpandable());
196           }
197         });
198       }
199     });
200   }
201
202   private static String truncateToMaxLength(String value) {
203     return value.substring(0, Math.min(value.length(), XValueNode.MAX_VALUE_LENGTH));
204   }
205
206   private static class JavaValuePresentation extends XValuePresentation implements XValueCompactPresentation {
207     private final String myValue;
208     private final String myType;
209     private final String myError;
210
211     public JavaValuePresentation(@NotNull String value, @Nullable String type, @Nullable String error) {
212       myValue = value;
213       myType = type;
214       myError = error;
215     }
216
217     @Nullable
218     @Override
219     public String getType() {
220       return myType;
221     }
222
223     @Override
224     public void renderValue(@NotNull XValueTextRenderer renderer) {
225       renderValue(renderer, null);
226     }
227
228     @Override
229     public void renderValue(@NotNull XValueTextRenderer renderer, @Nullable XValueNodeImpl node) {
230       boolean compact = node != null;
231       if (myError != null) {
232         if (myValue.endsWith(myError)) {
233           renderer.renderValue(myValue.substring(0, myValue.length() - myError.length()));
234         }
235         renderer.renderError(myError);
236       }
237       else {
238         if (compact && node.getValueContainer() instanceof JavaValue) {
239           final JavaValue container = (JavaValue)node.getValueContainer();
240
241           if (container.getDescriptor().isArray()) {
242             final ArrayReference value = (ArrayReference)container.getDescriptor().getValue();
243             final ArrayType type = (ArrayType)container.getDescriptor().getType();
244             if (type != null) {
245               final String typeName = type.componentTypeName();
246               if (TypeConversionUtil.isPrimitive(typeName) || CommonClassNames.JAVA_LANG_STRING.equals(typeName)) {
247                 int max = CommonClassNames.JAVA_LANG_STRING.equals(typeName) ? 5 : 10;
248                 final List<Value> values = value.getValues();
249                 int i = 0;
250                 final List<String> vals = new ArrayList<String>(max);
251                 while (i < values.size() && i <= max) {
252                   vals.add(StringUtil.first(values.get(i).toString(), 15, true));
253                   i++;
254                 }
255                 String more = "";
256                 if (vals.size() < values.size()) {
257                   more = ", + " + (values.size() - vals.size()) + " more";
258                 }
259
260                 renderer.renderValue("{" + StringUtil.join(vals, ", ") + more + "}");
261                 return;
262               }
263             }
264           }
265         }
266         renderer.renderValue(myValue);
267       }
268     }
269   }
270
271   String getValueString() {
272     return splitValue(myValueDescriptor.getValueLabel())[1];
273   }
274
275   private static class TypedStringValuePresentation extends XStringValuePresentation {
276     private final String myType;
277
278     public TypedStringValuePresentation(@NotNull String value, @Nullable String type) {
279       super(value);
280       myType = type;
281     }
282
283     @Nullable
284     @Override
285     public String getType() {
286       return myType;
287     }
288   }
289
290   private static String[] splitValue(String value) {
291     if (StringUtil.startsWithChar(value, '{')) {
292       int end = value.indexOf('}');
293       if (end > 0) {
294         return new String[]{value.substring(1, end), value.substring(end+1)};
295       }
296     }
297     return new String[]{null, value};
298   }
299
300   private int currentStart = 0;
301
302   @Override
303   public void computeChildren(@NotNull final XCompositeNode node) {
304     scheduleCommand(myEvaluationContext, node, new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
305       @Override
306       public Priority getPriority() {
307         return Priority.NORMAL;
308       }
309
310       @Override
311       public void contextAction() throws Exception {
312         final XValueChildrenList children = new XValueChildrenList();
313         final NodeRenderer renderer = myValueDescriptor.getRenderer(myEvaluationContext.getDebugProcess());
314         final Ref<Integer> remainingNum = new Ref<Integer>(0);
315         renderer.buildChildren(myValueDescriptor.getValue(), new ChildrenBuilder() {
316           @Override
317           public NodeDescriptorFactory getDescriptorManager() {
318             return myNodeManager;
319           }
320
321           @Override
322           public NodeManager getNodeManager() {
323             return myNodeManager;
324           }
325
326           @Override
327           public ValueDescriptor getParentDescriptor() {
328             return myValueDescriptor;
329           }
330
331           @Override
332           public void setRemaining(int remaining) {
333             remainingNum.set(remaining);
334           }
335
336           @Override
337           public void initChildrenArrayRenderer(ArrayRenderer renderer) {
338             renderer.START_INDEX = currentStart;
339             renderer.END_INDEX = currentStart + XCompositeNode.MAX_CHILDREN_TO_SHOW - 1;
340             currentStart += XCompositeNode.MAX_CHILDREN_TO_SHOW;
341           }
342
343           @Override
344           public void setChildren(List<DebuggerTreeNode> nodes) {
345             for (DebuggerTreeNode node : nodes) {
346               final NodeDescriptor descriptor = node.getDescriptor();
347               if (descriptor instanceof ValueDescriptorImpl) {
348                 // Value is calculated already in NodeManagerImpl
349                 children.add(create(JavaValue.this, (ValueDescriptorImpl)descriptor, myEvaluationContext, myNodeManager, false));
350               }
351               else if (descriptor instanceof MessageDescriptor) {
352                 children.add(new JavaStackFrame.DummyMessageValueNode(descriptor.getLabel(), null));
353               }
354             }
355           }
356         }, myEvaluationContext);
357         node.addChildren(children, true);
358         if (remainingNum.get() > 0) {
359           node.tooManyChildren(remainingNum.get());
360         }
361       }
362     });
363   }
364
365   protected static boolean scheduleCommand(EvaluationContextImpl evaluationContext,
366                                         @NotNull final XCompositeNode node,
367                                         final SuspendContextCommandImpl command) {
368     evaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(command.getSuspendContext()) {
369       @Override
370       public void contextAction() throws Exception {
371         command.contextAction();
372       }
373
374       @Override
375       protected void commandCancelled() {
376         node.setErrorMessage(DebuggerBundle.message("error.context.has.changed"));
377       }
378     });
379     return true;
380   }
381
382   @Override
383   public void computeSourcePosition(@NotNull final XNavigatable navigatable) {
384     myEvaluationContext.getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
385       @Override
386       public Priority getPriority() {
387         return Priority.NORMAL;
388       }
389
390       @Override
391       protected void commandCancelled() {
392         navigatable.setSourcePosition(null);
393       }
394
395       @Override
396       public void contextAction() throws Exception {
397         ApplicationManager.getApplication().runReadAction(new Runnable() {
398           @Override
399           public void run() {
400             final boolean nearest = navigatable instanceof XNearestSourcePosition;
401             if (myValueDescriptor instanceof FieldDescriptorImpl) {
402               SourcePosition position = ((FieldDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext(), nearest);
403               if (position != null) {
404                 navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
405               }
406             }
407             if (myValueDescriptor instanceof LocalVariableDescriptorImpl) {
408               SourcePosition position =
409                 ((LocalVariableDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext(), nearest);
410               if (position != null) {
411                 navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
412               }
413             }
414           }
415         });
416       }
417     });
418   }
419
420   private DebuggerContextImpl getDebuggerContext() {
421     return myEvaluationContext.getDebugProcess().getDebuggerContext();
422   }
423
424   public Project getProject() {
425     return myEvaluationContext.getProject();
426   }
427
428   @Override
429   public boolean canNavigateToTypeSource() {
430     return true;
431   }
432
433   @Override
434   public void computeTypeSourcePosition(@NotNull final XNavigatable navigatable) {
435     if (myEvaluationContext.getSuspendContext().isResumed()) return;
436     DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess();
437     debugProcess.getManagerThread().schedule(new JumpToObjectAction.NavigateCommand(getDebuggerContext(), myValueDescriptor, debugProcess, null) {
438       @Override
439       public Priority getPriority() {
440         return Priority.HIGH;
441       }
442
443       @Override
444       protected void doAction(@Nullable final SourcePosition sourcePosition) {
445         if (sourcePosition != null) {
446           ApplicationManager.getApplication().runReadAction(new Runnable() {
447             @Override
448             public void run() {
449               navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(sourcePosition));
450             }
451           });
452         }
453       }
454     });
455   }
456
457   @Nullable
458   @Override
459   public XValueModifier getModifier() {
460     return myValueDescriptor.canSetValue() ? new JavaValueModifier(this) : null;
461   }
462
463
464   private volatile String evaluationExpression = null;
465   @Nullable
466   @Override
467   public String getEvaluationExpression() {
468     if (evaluationExpression == null) {
469       // TODO: change API to allow to calculate it asynchronously
470       myEvaluationContext.getManagerThread().invokeAndWait(new DebuggerCommandImpl() {
471         @Override
472         public Priority getPriority() {
473           return Priority.HIGH;
474         }
475
476         @Override
477         protected void action() throws Exception {
478           evaluationExpression = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
479             @Override
480             public String compute() {
481               try {
482                 PsiExpression psiExpression = getDescriptor().getTreeEvaluation(JavaValue.this, getDebuggerContext());
483                 if (psiExpression != null) {
484                   return new TextWithImportsImpl(psiExpression).getText();
485                 }
486               }
487               catch (EvaluateException e) {
488                 LOG.info(e);
489               }
490               return null;
491             }
492           });
493         }
494       });
495     }
496     return evaluationExpression;
497   }
498
499   @Override
500   public String getValueText() {
501     return myValueDescriptor.getValueText();
502   }
503   @Nullable
504   @Override
505   public XReferrersProvider getReferrersProvider() {
506     return new XReferrersProvider() {
507       @Override
508       public XValue getReferringObjectsValue() {
509         return new JavaReferringObjectsValue(JavaValue.this, false);
510       }
511     };
512   }
513
514   @Nullable
515   @Override
516   public XInstanceEvaluator getInstanceEvaluator() {
517     return new XInstanceEvaluator() {
518       @Override
519       public void evaluate(@NotNull final XDebuggerEvaluator.XEvaluationCallback callback, @NotNull final XStackFrame frame) {
520         myEvaluationContext.getManagerThread().schedule(new DebuggerCommandImpl() {
521           @Override
522           protected void commandCancelled() {
523             callback.errorOccurred(DebuggerBundle.message("error.context.has.changed"));
524           }
525
526           @Override
527           protected void action() throws Exception {
528             ValueDescriptorImpl inspectDescriptor = myValueDescriptor;
529             if (myValueDescriptor instanceof WatchItemDescriptor) {
530               inspectDescriptor = (ValueDescriptorImpl) ((WatchItemDescriptor) myValueDescriptor).getModifier().getInspectItem(getProject());
531             }
532             EvaluationContextImpl evaluationContext = ((JavaStackFrame)frame).getFrameDebuggerContext().createEvaluationContext();
533             if (evaluationContext != null) {
534               callback.evaluated(create(inspectDescriptor, evaluationContext, myNodeManager));
535             }
536             else {
537               callback.errorOccurred("Context is not available");
538             }
539           }
540         });
541       }
542     };
543   }
544 }