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