2 * Copyright 2000-2017 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.debugger.memory.ui;
18 import com.intellij.debugger.DebuggerManager;
19 import com.intellij.debugger.engine.DebugProcessImpl;
20 import com.intellij.debugger.engine.DebuggerUtils;
21 import com.intellij.debugger.engine.JavaValue;
22 import com.intellij.debugger.engine.SuspendContextImpl;
23 import com.intellij.debugger.engine.evaluation.EvaluationContext;
24 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
25 import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
26 import com.intellij.debugger.memory.filtering.FilteringResult;
27 import com.intellij.debugger.memory.filtering.FilteringTask;
28 import com.intellij.debugger.memory.filtering.FilteringTaskCallback;
29 import com.intellij.debugger.memory.utils.*;
30 import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
31 import com.intellij.debugger.ui.impl.watch.MessageDescriptor;
32 import com.intellij.debugger.ui.impl.watch.NodeManagerImpl;
33 import com.intellij.debugger.ui.tree.NodeDescriptor;
34 import com.intellij.openapi.Disposable;
35 import com.intellij.openapi.actionSystem.*;
36 import com.intellij.openapi.actionSystem.ex.AnActionListener;
37 import com.intellij.openapi.application.ApplicationManager;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.project.Project;
40 import com.intellij.openapi.ui.DialogWrapper;
41 import com.intellij.openapi.util.Disposer;
42 import com.intellij.openapi.wm.IdeFocusManager;
43 import com.intellij.ui.DoubleClickListener;
44 import com.intellij.ui.JBColor;
45 import com.intellij.ui.components.JBLabel;
46 import com.intellij.ui.components.JBPanel;
47 import com.intellij.util.ui.JBDimension;
48 import com.intellij.util.ui.JBUI;
49 import com.intellij.util.ui.UIUtil;
50 import com.intellij.util.ui.update.UiNotifyConnector;
51 import com.intellij.xdebugger.XDebugSession;
52 import com.intellij.xdebugger.XDebugSessionListener;
53 import com.intellij.xdebugger.XExpression;
54 import com.intellij.xdebugger.frame.XValue;
55 import com.intellij.xdebugger.frame.XValueChildrenList;
56 import com.intellij.xdebugger.impl.XDebugSessionImpl;
57 import com.intellij.xdebugger.impl.actions.XDebuggerActionBase;
58 import com.intellij.xdebugger.impl.frame.XValueMarkers;
59 import com.intellij.xdebugger.impl.ui.XDebuggerExpressionEditor;
60 import com.intellij.xdebugger.impl.ui.tree.ValueMarkup;
61 import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState;
62 import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
63 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
64 import com.sun.jdi.ObjectReference;
65 import com.sun.jdi.Value;
66 import org.jetbrains.annotations.NotNull;
67 import org.jetbrains.annotations.Nullable;
68 import org.jetbrains.java.debugger.JavaDebuggerEditorsProvider;
71 import javax.swing.tree.TreeNode;
73 import java.awt.event.MouseEvent;
74 import java.util.List;
75 import java.util.concurrent.TimeUnit;
77 public class InstancesWindow extends DialogWrapper {
78 private static final Logger LOG = Logger.getInstance(InstancesWindow.class);
80 private static final int DEFAULT_WINDOW_WIDTH = 870;
81 private static final int DEFAULT_WINDOW_HEIGHT = 400;
82 private static final int FILTERING_BUTTON_ADDITIONAL_WIDTH = 30;
83 private static final int BORDER_LAYOUT_DEFAULT_GAP = 5;
84 private static final int DEFAULT_INSTANCES_LIMIT = 500000;
86 private final Project myProject;
87 private final DebugProcessImpl myDebugProcess;
88 private final InstancesProvider myInstancesProvider;
89 private final String myClassName;
90 private final MyInstancesView myInstancesView;
92 public InstancesWindow(@NotNull XDebugSession session,
93 @NotNull InstancesProvider provider,
94 @NotNull String className) {
95 super(session.getProject(), false);
97 myProject = session.getProject();
98 myDebugProcess = (DebugProcessImpl)DebuggerManager.getInstance(myProject)
99 .getDebugProcess(session.getDebugProcess().getProcessHandler());
100 myInstancesProvider = provider;
101 myClassName = className;
103 addWarningMessage(null);
104 session.addSessionListener(new XDebugSessionListener() {
106 public void sessionStopped() {
107 ApplicationManager.getApplication().invokeLater(() -> close(OK_EXIT_CODE));
111 myInstancesView = new MyInstancesView(session);
112 myInstancesView.setPreferredSize(
113 new JBDimension(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT));
117 JRootPane root = myInstancesView.getRootPane();
118 root.setDefaultButton(myInstancesView.myFilterButton);
121 private void addWarningMessage(@Nullable String message) {
122 String warning = message == null ? "" : String.format(". Warning: %s", message);
123 setTitle(String.format("Instances of %s%s", myClassName, warning));
128 protected String getDimensionServiceKey() {
129 return "#org.jetbrains.debugger.memory.view.InstancesWindow";
134 protected JComponent createCenterPanel() {
135 return myInstancesView;
140 protected JComponent createSouthPanel() {
141 JComponent comp = super.createSouthPanel();
143 comp.add(myInstancesView.myProgress, BorderLayout.WEST);
151 protected Action[] createActions() {
152 return new Action[]{new DialogWrapperExitAction("Close", CLOSE_EXIT_CODE)};
155 private class MyInstancesView extends JBPanel implements Disposable {
156 private static final int MAX_TREE_NODE_COUNT = 2000;
157 private static final int FILTERING_CHUNK_SIZE = 50;
159 private static final int MAX_DURATION_TO_UPDATE_TREE_SECONDS = 3;
160 private static final int FILTERING_PROGRESS_UPDATING_MIN_DELAY_MILLIS = 17; // ~ 60 fps
162 private final InstancesTree myInstancesTree;
163 private final XDebuggerExpressionEditor myFilterConditionEditor;
164 private final XDebugSessionListener myDebugSessionListener = new MySessionListener();
166 private final MyNodeManager myNodeManager = new MyNodeManager(myProject);
168 private final JButton myFilterButton = new JButton("Filter");
169 private final FilteringProgressView myProgress = new FilteringProgressView();
171 private final Object myFilteringTaskLock = new Object();
173 private boolean myIsAndroidVM = false;
175 private volatile MyFilteringWorker myFilteringTask = null;
177 MyInstancesView(@NotNull XDebugSession session) {
178 super(new BorderLayout(0, JBUI.scale(BORDER_LAYOUT_DEFAULT_GAP)));
180 Disposer.register(InstancesWindow.this.myDisposable, this);
181 final XValueMarkers<?, ?> markers = getValueMarkers(session);
182 if (markers != null) {
183 final MyActionListener listener = new MyActionListener(markers);
184 ActionManager.getInstance().addAnActionListener(listener, InstancesWindow.this.myDisposable);
186 session.addSessionListener(myDebugSessionListener, InstancesWindow.this.myDisposable);
187 final JavaDebuggerEditorsProvider editorsProvider = new JavaDebuggerEditorsProvider();
189 myFilterConditionEditor = new ExpressionEditorWithHistory(myProject, myClassName,
190 editorsProvider, InstancesWindow.this.myDisposable);
192 final Dimension filteringButtonSize = myFilterConditionEditor.getEditorComponent().getPreferredSize();
193 filteringButtonSize.width = JBUI.scale(FILTERING_BUTTON_ADDITIONAL_WIDTH) +
194 myFilterButton.getPreferredSize().width;
195 myFilterButton.setPreferredSize(filteringButtonSize);
197 final JBPanel filteringPane = new JBPanel(new BorderLayout(JBUI.scale(BORDER_LAYOUT_DEFAULT_GAP), 0));
198 final JBLabel sideEffectsWarning = new JBLabel("Warning: filtering may have side effects", SwingConstants.RIGHT);
199 sideEffectsWarning.setBorder(JBUI.Borders.emptyTop(1));
200 sideEffectsWarning.setComponentStyle(UIUtil.ComponentStyle.SMALL);
201 sideEffectsWarning.setFontColor(UIUtil.FontColor.BRIGHTER);
203 filteringPane.add(new JBLabel("Condition:"), BorderLayout.WEST);
204 filteringPane.add(myFilterConditionEditor.getComponent(), BorderLayout.CENTER);
205 filteringPane.add(myFilterButton, BorderLayout.EAST);
206 filteringPane.add(sideEffectsWarning, BorderLayout.SOUTH);
208 myProgress.addStopActionListener(this::cancelFilteringTask);
210 myInstancesTree = new InstancesTree(myProject, editorsProvider, markers, this::updateInstances);
212 myFilterButton.addActionListener(e -> {
213 final String expression = myFilterConditionEditor.getExpression().getExpression();
214 if (!expression.isEmpty()) {
215 myFilterConditionEditor.saveTextInHistory();
218 myFilterButton.setEnabled(false);
219 myInstancesTree.rebuildTree(InstancesTree.RebuildPolicy.RELOAD_INSTANCES);
223 final StackFrameList list = new StackFrameList(myDebugProcess);
225 list.addListSelectionListener(e -> list.navigateToSelectedValue(false));
226 new DoubleClickListener() {
228 protected boolean onDoubleClick(MouseEvent event) {
229 list.navigateToSelectedValue(true);
234 final InstancesWithStackFrameView instancesWithStackFrame = new InstancesWithStackFrameView(session,
235 myInstancesTree, list, myClassName);
237 add(filteringPane, BorderLayout.NORTH);
238 add(instancesWithStackFrame.getComponent(), BorderLayout.CENTER);
240 final JComponent focusedComponent = myFilterConditionEditor.getEditorComponent();
241 UiNotifyConnector.doWhenFirstShown(focusedComponent, () ->
242 IdeFocusManager.findInstanceByComponent(focusedComponent)
243 .requestFocus(focusedComponent, true));
247 public void dispose() {
248 cancelFilteringTask();
249 Disposer.dispose(myInstancesTree);
252 private void updateInstances() {
253 cancelFilteringTask();
254 final XExpression filteringExpression = myFilterConditionEditor.getExpression();
256 myDebugProcess.getManagerThread().schedule(new DebuggerContextCommandImpl(myDebugProcess.getDebuggerContext()) {
258 public Priority getPriority() {
259 return Priority.LOWEST;
263 public void threadAction(@NotNull SuspendContextImpl suspendContext) {
264 myIsAndroidVM = DebuggerUtils.isAndroidVM(myDebugProcess.getVirtualMachineProxy().getVirtualMachine());
265 final int limit = myIsAndroidVM
266 ? AndroidUtil.ANDROID_INSTANCES_LIMIT
267 : DEFAULT_INSTANCES_LIMIT;
268 List<ObjectReference> instances = myInstancesProvider.getInstances(limit + 1);
270 final EvaluationContextImpl evaluationContext = myDebugProcess
271 .getDebuggerContext().createEvaluationContext();
273 if (instances.size() > limit) {
274 addWarningMessage(String.format("Not all instances will be loaded (only %d)", limit));
275 instances = instances.subList(0, limit);
278 if (evaluationContext != null) {
279 synchronized (myFilteringTaskLock) {
280 myFilteringTask = new MyFilteringWorker(instances, filteringExpression, evaluationContext);
281 myFilteringTask.execute();
288 private void cancelFilteringTask() {
289 if (myFilteringTask != null) {
290 synchronized (myFilteringTaskLock) {
291 if (myFilteringTask != null) {
292 myFilteringTask.cancel();
293 myFilteringTask = null;
299 private XValueMarkers<?, ?> getValueMarkers(@NotNull XDebugSession session) {
300 return session instanceof XDebugSessionImpl
301 ? ((XDebugSessionImpl)session).getValueMarkers()
305 private class MySessionListener implements XDebugSessionListener {
306 private volatile XDebuggerTreeState myTreeState = null;
309 public void sessionResumed() {
310 ApplicationManager.getApplication().invokeLater(() -> {
311 myTreeState = XDebuggerTreeState.saveState(myInstancesTree);
312 cancelFilteringTask();
314 myInstancesTree.setInfoMessage(
315 "The application is running");
320 public void sessionPaused() {
321 ApplicationManager.getApplication().invokeLater(() -> {
322 myProgress.setVisible(true);
323 final XDebuggerTreeState state = myTreeState;
325 myInstancesTree.rebuildTree(InstancesTree.RebuildPolicy.RELOAD_INSTANCES, state);
328 myInstancesTree.rebuildTree(InstancesTree.RebuildPolicy.RELOAD_INSTANCES);
334 private class MyActionListener extends AnActionListener.Adapter {
335 private final XValueMarkers<?, ?> myValueMarkers;
337 private MyActionListener(@NotNull XValueMarkers<?, ?> markers) {
338 myValueMarkers = markers;
342 public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
343 if (dataContext.getData(PlatformDataKeys.CONTEXT_COMPONENT) == myInstancesTree &&
344 (isAddToWatchesAction(action) || isEvaluateExpressionAction(action))) {
345 XValueNodeImpl selectedNode = XDebuggerTreeActionBase.getSelectedNode(dataContext);
347 if (selectedNode != null) {
348 TreeNode currentNode = selectedNode;
349 while (!myInstancesTree.getRoot().equals(currentNode.getParent())) {
350 currentNode = currentNode.getParent();
353 final XValue valueContainer = ((XValueNodeImpl)currentNode).getValueContainer();
355 final String expression = valueContainer.getEvaluationExpression();
356 if (expression != null) {
357 myValueMarkers.markValue(valueContainer,
358 new ValueMarkup(expression.replace("@", ""), new JBColor(0, 0), null));
361 ApplicationManager.getApplication().invokeLater(() -> myInstancesTree
362 .rebuildTree(InstancesTree.RebuildPolicy.ONLY_UPDATE_LABELS));
367 private boolean isAddToWatchesAction(AnAction action) {
368 final String className = action.getClass().getSimpleName();
369 return action instanceof XDebuggerTreeActionBase && className.equals("XAddToWatchesAction");
372 private boolean isEvaluateExpressionAction(AnAction action) {
373 final String className = action.getClass().getSimpleName();
374 return action instanceof XDebuggerActionBase && className.equals("EvaluateAction");
378 private class MyFilteringCallback implements FilteringTaskCallback {
379 private final ErrorsValueGroup myErrorsGroup = new ErrorsValueGroup();
380 private final EvaluationContextImpl myEvaluationContext;
382 private long myFilteringStartedTime;
384 private int myProceedCount = 0;
385 private int myMatchedCount = 0;
386 private int myErrorsCount = 0;
388 private long myLastTreeUpdatingTime;
389 private long myLastProgressUpdatingTime;
391 public MyFilteringCallback(@NotNull EvaluationContextImpl evaluationContext) {
392 myEvaluationContext = evaluationContext;
395 private XValueChildrenList myChildren = new XValueChildrenList();
398 public void started(int total) {
399 myFilteringStartedTime = System.nanoTime();
400 myLastTreeUpdatingTime = myFilteringStartedTime;
401 myLastProgressUpdatingTime = System.nanoTime();
402 ApplicationManager.getApplication().invokeLater(() -> myProgress.start(total));
407 public Action matched(@NotNull Value ref) {
408 final JavaValue val = new InstanceJavaValue(new InstanceValueDescriptor(myProject, ref),
409 myEvaluationContext, myNodeManager);
416 return myMatchedCount < MAX_TREE_NODE_COUNT ? Action.CONTINUE : Action.STOP;
421 public Action notMatched(@NotNull Value ref) {
425 return Action.CONTINUE;
430 public Action error(@NotNull Value ref, @NotNull String description) {
431 final JavaValue val = new InstanceJavaValue(new InstanceValueDescriptor(myProject, ref),
432 myEvaluationContext, myNodeManager);
433 myErrorsGroup.addErrorValue(description, val);
437 return Action.CONTINUE;
441 public void completed(@NotNull FilteringResult reason) {
442 if (!myErrorsGroup.isEmpty()) {
443 myChildren.addBottomGroup(myErrorsGroup);
446 final long duration = System.nanoTime() - myFilteringStartedTime;
447 LOG.info(String.format("Filtering completed in %d ms for %d instances",
448 TimeUnit.NANOSECONDS.toMillis(duration),
451 final int proceed = myProceedCount;
452 final int matched = myMatchedCount;
453 final int errors = myErrorsCount;
454 final XValueChildrenList childrenList = myChildren;
455 ApplicationManager.getApplication().invokeLater(() -> {
456 myProgress.updateProgress(proceed, matched, errors);
457 myInstancesTree.addChildren(childrenList, true);
458 myFilterButton.setEnabled(true);
459 myProgress.complete(reason);
463 private void updateProgress() {
464 final long now = System.nanoTime();
465 if (now - myLastProgressUpdatingTime > TimeUnit.MILLISECONDS.toNanos(FILTERING_PROGRESS_UPDATING_MIN_DELAY_MILLIS)) {
466 final int proceed = myProceedCount;
467 final int matched = myMatchedCount;
468 final int errors = myErrorsCount;
469 ApplicationManager.getApplication().invokeLater(() -> myProgress.updateProgress(proceed, matched, errors));
470 myLastProgressUpdatingTime = now;
474 private void updateTree() {
475 final long now = System.nanoTime();
476 final int newChildrenCount = myChildren.size();
477 if (newChildrenCount >= FILTERING_CHUNK_SIZE ||
478 (newChildrenCount > 0 && now - myLastTreeUpdatingTime > TimeUnit.SECONDS.toNanos(MAX_DURATION_TO_UPDATE_TREE_SECONDS))) {
479 final XValueChildrenList children = myChildren;
480 ApplicationManager.getApplication().invokeLater(() -> myInstancesTree.addChildren(children, false));
481 myChildren = new XValueChildrenList();
482 myLastTreeUpdatingTime = System.nanoTime();
487 private class MyFilteringWorker extends SwingWorker<Void, Void> {
488 private final FilteringTask myTask;
490 MyFilteringWorker(@NotNull List<ObjectReference> refs,
491 @NotNull XExpression expression,
492 @NotNull EvaluationContextImpl evaluationContext) {
493 myTask = new FilteringTask(myClassName, myDebugProcess, expression, new MyValuesList(refs),
494 new MyFilteringCallback(evaluationContext));
498 protected Void doInBackground() throws Exception {
501 } catch (Throwable e) {
507 public void cancel() {
514 private static class MyValuesList implements FilteringTask.ValuesList {
515 private final List<ObjectReference> myRefs;
517 public MyValuesList(List<ObjectReference> refs) {
523 return myRefs.size();
527 public ObjectReference get(int index) {
528 return myRefs.get(index);
532 private final static class MyNodeManager extends NodeManagerImpl {
533 MyNodeManager(Project project) {
534 super(project, null);
539 public DebuggerTreeNodeImpl createNode(final NodeDescriptor descriptor, EvaluationContext evaluationContext) {
540 return new DebuggerTreeNodeImpl(null, descriptor);
544 public DebuggerTreeNodeImpl createMessageNode(MessageDescriptor descriptor) {
545 return new DebuggerTreeNodeImpl(null, descriptor);
550 public DebuggerTreeNodeImpl createMessageNode(String message) {
551 return new DebuggerTreeNodeImpl(null, new MessageDescriptor(message));