myConstructorTrackedClasses = new ConcurrentHashMap<>();
+ private final XDebugSessionListener myDebugSessionListener;
+
+ @Nullable
+ private volatile SuspendContextImpl myLastSuspendContext;
+
+ /**
+ * Indicates that the debug session had been stopped at least once.
+ *
+ * State: false to true
+ */
+ private volatile boolean myIsTrackersActivated = false;
+
+ /**
+ * Indicates that view is visible in tool window
+ */
+ private boolean myIsActive;
+
+ public ClassesFilteredView(@NotNull XDebugSession debugSession) {
+ super();
+
+ myProject = debugSession.getProject();
+ myDebugSession = debugSession;
+ myDebugProcess = (DebugProcessImpl) DebuggerManager.getInstance(myProject)
+ .getDebugProcess(myDebugSession.getDebugProcess().getProcessHandler());
+
+ if (myDebugProcess == null) {
+ throw new NullPointerException("Failed to receive a java debug process");
+ }
+
+ myInstancesTracker = InstancesTracker.getInstance(myProject);
+ InstancesTrackerListener instancesTrackerListener = new InstancesTrackerListener() {
+ @Override
+ public void classChanged(@NotNull String name, @NotNull TrackingType type) {
+ ReferenceType ref = myTable.getClassByName(name);
+ if (ref != null) {
+ myDebugProcess.getManagerThread()
+ .schedule(new LowestPriorityCommand(getSuspendContext()) {
+ @Override
+ public void contextAction(@NotNull SuspendContextImpl suspendContext) throws Exception {
+ trackClass(ref, type, myIsTrackersActivated);
+ }
+ });
+ }
+ myTable.repaint();
+ }
+
+ @Override
+ public void classRemoved(@NotNull String name) {
+ ReferenceType ref = myTable.getClassByName(name);
+ if (ref != null && myConstructorTrackedClasses.containsKey(ref)) {
+ ConstructorInstancesTracker removed = myConstructorTrackedClasses.remove(ref);
+ Disposer.dispose(removed);
+ myTable.getRowSorter().allRowsChanged();
+ }
+ }
+ };
+
+ myDebugProcess.getManagerThread().schedule(new DebuggerCommandImpl() {
+ @Override
+ public Priority getPriority() {
+ return Priority.LOWEST;
+ }
+
+ @Override
+ protected void action() throws Exception {
+ for (Map.Entry entry : myInstancesTracker.getTrackedClasses().entrySet()) {
+ TrackingType type = entry.getValue();
+ String className = entry.getKey();
+ List classes = myDebugProcess.getVirtualMachineProxy().classesByName(className);
+ if (classes.isEmpty()) {
+ new ClassPreparedListener(className, myDebugSession) {
+ @Override
+ public void onClassPrepared(@NotNull ReferenceType referenceType) {
+ trackClass(referenceType, type, myIsTrackersActivated);
+ }
+ };
+ } else {
+ for (ReferenceType ref : classes) {
+ trackClass(ref, type, myIsTrackersActivated);
+ }
+ }
+ }
+
+ myInstancesTracker.addTrackerListener(instancesTrackerListener, ClassesFilteredView.this);
+ }
+ });
+
+ MemoryViewManagerState memoryViewManagerState = MemoryViewManager.getInstance().getState();
+
+ myTable = new ClassesTable(myDebugSession, memoryViewManagerState.isShowWithDiffOnly,
+ memoryViewManagerState.isShowWithInstancesOnly, memoryViewManagerState.isShowTrackedOnly, this);
+ myTable.getEmptyText().setText(EMPTY_TABLE_CONTENT_WHEN_RUNNING);
+ Disposer.register(this, myTable);
+
+ myTable.addMouseMotionListener(new MyMouseMotionListener());
+ myTable.addMouseListener(new MyOpenNewInstancesListener());
+ new MyDoubleClickListener().installOn(myTable);
+
+ myTable.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyReleased(KeyEvent e) {
+ int keyCode = e.getKeyCode();
+ if (KeyboardUtils.isEnterKey(keyCode)) {
+ handleClassSelection(myTable.getSelectedClass());
+ } else if (KeyboardUtils.isCharacter(keyCode) || KeyboardUtils.isBackSpace(keyCode)) {
+ String text = myFilterTextField.getText();
+ String newText = KeyboardUtils.isBackSpace(keyCode)
+ ? text.substring(0, text.length() - 1)
+ : text + e.getKeyChar();
+ myFilterTextField.setText(newText);
+ FocusManager.getCurrentManager().focusNextComponent(myFilterTextField);
+ }
+ }
+ });
+
+ myFilterTextField.addKeyboardListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ dispatch(e);
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ dispatch(e);
+ }
+
+ private void dispatch(KeyEvent e) {
+ if (KeyboardUtils.isUpDownKey(e.getKeyCode()) || KeyboardUtils.isEnterKey(e.getKeyCode())) {
+ myTable.dispatchEvent(e);
+ }
+ }
+ });
+
+ myFilterTextField.addDocumentListener(new DocumentAdapter() {
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ myTable.setFilterPattern(myFilterTextField.getText());
+ }
+ });
+
+ final MemoryViewManagerListener memoryViewManagerListener = state -> {
+ myTable.setFilteringByDiffNonZero(state.isShowWithDiffOnly);
+ myTable.setFilteringByInstanceExists(state.isShowWithInstancesOnly);
+ myTable.setFilteringByTrackingState(state.isShowTrackedOnly);
+ };
+
+ MemoryViewManager.getInstance().addMemoryViewManagerListener(memoryViewManagerListener, this);
+
+ myDebugSessionListener = new XDebugSessionListener() {
+ @Override
+ public void sessionResumed() {
+ myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::obsolete);
+ ApplicationManager.getApplication().invokeLater(() -> {
+ myTable.getEmptyText().setText(EMPTY_TABLE_CONTENT_WHEN_RUNNING);
+ myTable.hideContent();
+ });
+ mySingleAlarm.cancelAllRequests();
+ }
+
+ @Override
+ public void sessionStopped() {
+ debugSession.removeSessionListener(this);
+ }
+
+ @Override
+ public void sessionPaused() {
+ ApplicationManager.getApplication().invokeLater(() -> myTable.getEmptyText().setText(EMPTY_TABLE_CONTENT_WHEN_SUSPENDED));
+ updateClassesAndCounts();
+ }
+ };
+
+ mySingleAlarm = new SingleAlarmWithMutableDelay(() -> {
+ myLastSuspendContext = getSuspendContext();
+ if (myLastSuspendContext != null) {
+ ApplicationManager.getApplication().invokeLater(() -> myTable.setBusy(true));
+ myDebugProcess.getManagerThread()
+ .schedule(new MyUpdateClassesCommand(myLastSuspendContext));
+ }
+ }, this);
+
+ myTable.addMouseListener(new PopupHandler() {
+ @Override
+ public void invokePopup(Component comp, int x, int y) {
+ ActionPopupMenu menu = createContextMenu();
+ if (menu != null) {
+ menu.getComponent().show(comp, x, y);
+ }
+ }
+ });
+
+ JScrollPane scroll = ScrollPaneFactory.createScrollPane(myTable, SideBorder.TOP);
+ addToTop(myFilterTextField);
+ addToCenter(scroll);
+ }
+
+ @Nullable
+ TrackerForNewInstances getStrategy(@NotNull ReferenceType ref) {
+ return myConstructorTrackedClasses.getOrDefault(ref, null);
+ }
+
+ private void trackClass(@NotNull ReferenceType ref,
+ @NotNull TrackingType type,
+ boolean isTrackerEnabled) {
+ LOG.assertTrue(DebuggerManager.getInstance(myProject).isDebuggerManagerThread());
+ if (type == TrackingType.CREATION) {
+ ConstructorInstancesTracker old = myConstructorTrackedClasses.getOrDefault(ref, null);
+ if (old != null) {
+ Disposer.dispose(old);
+ }
+
+ ConstructorInstancesTracker tracker = new ConstructorInstancesTracker(ref, myDebugSession);
+ tracker.setBackgroundMode(!myIsActive);
+ if (isTrackerEnabled) {
+ tracker.enable();
+ } else {
+ tracker.disable();
+ }
+
+ Disposer.register(this, tracker);
+ myConstructorTrackedClasses.put(ref, tracker);
+ }
+ }
+
+ private void handleClassSelection(@Nullable ReferenceType ref) {
+ if (ref != null && myDebugSession.isSuspended()) {
+ new InstancesWindow(myDebugSession, ref::instances, ref.name()).show();
+ }
+ }
+
+ private void commitAllTrackers() {
+ myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::commitTracked);
+ }
+
+ private SuspendContextImpl getSuspendContext() {
+ return DebuggerManagerEx.getInstanceEx(myProject).getContext().getSuspendContext();
+ }
+
+ private void updateClassesAndCounts() {
+ if (myDebugProcess.isAttached()) {
+ mySingleAlarm.cancelAndRequest();
+ }
+ }
+
+ private static ActionPopupMenu createContextMenu() {
+ ActionGroup group = (ActionGroup) ActionManager.getInstance().getAction("MemoryView.ClassesPopupActionGroup");
+ return ActionManager.getInstance().createActionPopupMenu("MemoryView.ClassesPopupActionGroup", group);
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ public void setActive(boolean active) {
+ if (myIsActive == active) {
+ return;
+ }
+
+ myIsActive = active;
+ myDebugProcess.getManagerThread().schedule(new DebuggerCommandImpl() {
+ @Override
+ protected void action() throws Exception {
+ if (active) {
+ doActivate();
+ } else {
+ doPause();
+ }
+ }
+ });
+ }
+
+ private void doActivate() {
+ myDebugSession.addSessionListener(myDebugSessionListener, this);
+ myConstructorTrackedClasses.values().forEach(x -> x.setBackgroundMode(false));
+ final SuspendContextImpl lastContext = myLastSuspendContext;
+ if (lastContext == null || !lastContext.equals(getSuspendContext())) {
+ updateClassesAndCounts();
+ }
+ }
+
+ private void doPause() {
+ myDebugSession.removeSessionListener(myDebugSessionListener);
+ myConstructorTrackedClasses.values().forEach(x -> x.setBackgroundMode(true));
+ }
+
+ private final class MyUpdateClassesCommand extends LowestPriorityCommand {
+
+ MyUpdateClassesCommand(@Nullable SuspendContextImpl suspendContext) {
+ super(suspendContext);
+ }
+
+ @Override
+ public void contextAction(@NotNull SuspendContextImpl suspendContext) throws Exception {
+ if (!myIsTrackersActivated) {
+ myConstructorTrackedClasses.values().forEach(ConstructorInstancesTracker::enable);
+ myIsTrackersActivated = true;
+ } else {
+ commitAllTrackers();
+ }
+ final List classes = myDebugProcess.getVirtualMachineProxy().allClasses();
+
+ if (classes.isEmpty()) {
+ return;
+ }
+
+ VirtualMachine vm = classes.get(0).virtualMachine();
+ int batchSize = AndroidUtil.isAndroidVM(vm)
+ ? AndroidUtil.ANDROID_COUNT_BY_CLASSES_BATCH_SIZE
+ : DEFAULT_BATCH_SIZE;
+
+ List chunks = new SmartList<>();
+ int size = classes.size();
+ for (int begin = 0, end = Math.min(batchSize, size);
+ begin != size && isContextValid();
+ begin = end, end = Math.min(end + batchSize, size)) {
+ List batch = classes.subList(begin, end);
+
+ long start = System.nanoTime();
+ long[] counts = vm.instanceCounts(batch);
+ long delay = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
+
+ chunks.add(counts);
+
+ int waitTime = (int) Math.min(DELAY_BEFORE_INSTANCES_QUERY_COEFFICIENT * delay, MAX_DELAY_MILLIS);
+ mySingleAlarm.setDelay(waitTime);
+ LOG.info(String.format("Instances query time = %d ms. Count = %d", delay, batch.size()));
+ }
+
+ if (isContextValid()) {
+ final long[] counts = chunks.size() == 1 ? chunks.get(0) : IntStream.range(0, chunks.size()).boxed()
+ .flatMapToLong(integer -> Arrays.stream(chunks.get(integer)))
+ .toArray();
+ ApplicationManager.getApplication().invokeLater(() -> myTable.setClassesAndUpdateCounts(classes, counts));
+ }
+
+ ApplicationManager.getApplication().invokeLater(() -> myTable.setBusy(false));
+ }
+
+ private boolean isContextValid() {
+ return ClassesFilteredView.this.getSuspendContext() == getSuspendContext();
+ }
+ }
+
+ private static class FilterTextField extends SearchTextField {
+ FilterTextField() {
+ super(false);
+ }
+
+ @Override
+ protected void showPopup() {
+ }
+
+ @Override
+ protected boolean hasIconsOutsideOfTextField() {
+ return false;
+ }
+ }
+
+ private class MyOpenNewInstancesListener extends MouseAdapter {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() != 1 || e.getButton() != MouseEvent.BUTTON1 || !isShowNewInstancesEvent(e)) {
+ return;
+ }
+
+ final CreationPositionTracker tracker = CreationPositionTracker.getInstance(myDebugSession.getProject());
+ ReferenceType ref = myTable.getSelectedClass();
+ TrackerForNewInstances strategy = ref == null ? null : getStrategy(ref);
+ if (strategy != null && tracker != null) {
+ List newInstances = strategy.getNewInstances();
+ tracker.pinStacks(myDebugSession, ref);
+ InstancesWindow instancesWindow = new InstancesWindow(myDebugSession, limit -> newInstances, ref.name());
+ Disposer.register(instancesWindow.getDisposable(), () -> tracker.unpinStacks(myDebugSession, ref));
+ instancesWindow.show();
+ }
+ }
+ }
+
+ private class MyDoubleClickListener extends DoubleClickListener {
+ @Override
+ protected boolean onDoubleClick(MouseEvent event) {
+ if (!isShowNewInstancesEvent(event)) {
+ handleClassSelection(myTable.getSelectedClass());
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ private class MyMouseMotionListener implements MouseMotionListener {
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (isShowNewInstancesEvent(e)) {
+ myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ } else {
+ myTable.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ }
+
+ private boolean isShowNewInstancesEvent(@NotNull MouseEvent e) {
+ final int col = myTable.columnAtPoint(e.getPoint());
+ final int row = myTable.rowAtPoint(e.getPoint());
+ if (col == -1 || row == -1 || myTable.convertColumnIndexToModel(col) != DIFF_COLUMN_INDEX) {
+ return false;
+ }
+
+ final int modelRow = myTable.convertRowIndexToModel(row);
+
+ final ReferenceType ref = (ReferenceType) myTable.getModel().getValueAt(modelRow, CLASSNAME_COLUMN_INDEX);
+ final ConstructorInstancesTracker tracker = myConstructorTrackedClasses.getOrDefault(ref, null);
+
+ return tracker != null && tracker.isReady() && tracker.getCount() > 0;
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/ClassesTable.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/ClassesTable.java
new file mode 100644
index 000000000000..6c4bcb9cbb15
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/ClassesTable.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.debugger.memory.component.InstancesTracker;
+import com.intellij.debugger.memory.utils.AbstractTableColumnDescriptor;
+import com.intellij.debugger.memory.utils.AbstractTableModelWithColumns;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.DataKey;
+import com.intellij.openapi.actionSystem.DataProvider;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.codeStyle.MinusculeMatcher;
+import com.intellij.psi.codeStyle.NameUtil;
+import com.intellij.ui.ColoredTableCellRenderer;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.speedSearch.SpeedSearchUtil;
+import com.intellij.ui.table.JBTable;
+import com.intellij.util.containers.FList;
+import com.intellij.util.ui.JBDimension;
+import com.intellij.util.ui.JBUI;
+import com.intellij.xdebugger.XDebugSession;
+import com.sun.jdi.ObjectReference;
+import com.sun.jdi.ReferenceType;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import com.intellij.debugger.memory.tracking.TrackerForNewInstances;
+import com.intellij.debugger.memory.tracking.TrackingType;
+import com.intellij.debugger.memory.utils.InstancesProvider;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableRowSorter;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ClassesTable extends JBTable implements DataProvider, Disposable {
+ public static final DataKey SELECTED_CLASS_KEY = DataKey.create("ClassesTable.SelectedClass");
+ public static final DataKey DEBUG_SESSION_KEY = DataKey.create("ClassesTable.DebugSession");
+ public static final DataKey NEW_INSTANCES_PROVIDER_KEY =
+ DataKey.create("ClassesTable.NewInstances");
+ public static final DataKey REF_COUNT_PROVIDER_KEY =
+ DataKey.create("ClassesTable.ReferenceCountProvider");
+
+ private static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder();
+
+ private static final int CLASSES_COLUMN_PREFERRED_WIDTH = 250;
+ private static final int COUNT_COLUMN_MIN_WIDTH = 80;
+ private static final int DIFF_COLUMN_MIN_WIDTH = 80;
+
+ private final DiffViewTableModel myModel = new DiffViewTableModel();
+ private final UnknownDiffValue myUnknownValue = new UnknownDiffValue();
+ private final XDebugSession myDebugSession;
+ private final Map myCounts = new ConcurrentHashMap<>();
+ private final InstancesTracker myInstancesTracker;
+ private final ClassesFilteredView myParent;
+ private final ReferenceCountProvider myCountProvider;
+
+ private boolean myOnlyWithDiff;
+ private boolean myOnlyTracked;
+ private boolean myOnlyWithInstances;
+ private MinusculeMatcher myMatcher = NameUtil.buildMatcher("*").build();
+ private String myFilteringPattern = "";
+
+ private volatile List myItems = Collections.unmodifiableList(new ArrayList<>());
+
+ ClassesTable(@NotNull XDebugSession session, boolean onlyWithDiff, boolean onlyWithInstances,
+ boolean onlyTracked, @NotNull ClassesFilteredView parent) {
+ setModel(myModel);
+
+ myDebugSession = session;
+ myOnlyWithDiff = onlyWithDiff;
+ myOnlyWithInstances = onlyWithInstances;
+ myOnlyTracked = onlyTracked;
+ myInstancesTracker = InstancesTracker.getInstance(myDebugSession.getProject());
+ myParent = parent;
+
+ TableColumn classesColumn = getColumnModel().getColumn(DiffViewTableModel.CLASSNAME_COLUMN_INDEX);
+ TableColumn countColumn = getColumnModel().getColumn(DiffViewTableModel.COUNT_COLUMN_INDEX);
+ TableColumn diffColumn = getColumnModel().getColumn(DiffViewTableModel.DIFF_COLUMN_INDEX);
+
+ setAutoResizeMode(AUTO_RESIZE_NEXT_COLUMN);
+ classesColumn.setPreferredWidth(JBUI.scale(CLASSES_COLUMN_PREFERRED_WIDTH));
+
+ countColumn.setMinWidth(JBUI.scale(COUNT_COLUMN_MIN_WIDTH));
+
+ diffColumn.setMinWidth(JBUI.scale(DIFF_COLUMN_MIN_WIDTH));
+
+ setShowGrid(false);
+ setIntercellSpacing(new JBDimension(0, 0));
+
+ setDefaultRenderer(ReferenceType.class, new MyClassColumnRenderer());
+ setDefaultRenderer(Long.class, new MyCountColumnRenderer());
+ setDefaultRenderer(DiffValue.class, new MyDiffColumnRenderer());
+
+ TableRowSorter sorter = new TableRowSorter<>(myModel);
+ sorter.setRowFilter(new RowFilter() {
+ @Override
+ public boolean include(Entry extends DiffViewTableModel, ? extends Integer> entry) {
+ int ix = entry.getIdentifier();
+ ReferenceType ref = myItems.get(ix);
+ DiffValue diff = myCounts.getOrDefault(ref, myUnknownValue);
+
+ boolean isFilteringOptionsRefused = myOnlyWithDiff && diff.diff() == 0
+ || myOnlyWithInstances && !diff.hasInstance()
+ || myOnlyTracked && myParent.getStrategy(ref) == null;
+ return !(isFilteringOptionsRefused) && myMatcher.matches(ref.name());
+ }
+ });
+
+ List myDefaultSortingKeys = Arrays.asList(
+ new RowSorter.SortKey(DiffViewTableModel.DIFF_COLUMN_INDEX, SortOrder.DESCENDING),
+ new RowSorter.SortKey(DiffViewTableModel.COUNT_COLUMN_INDEX, SortOrder.DESCENDING),
+ new RowSorter.SortKey(DiffViewTableModel.CLASSNAME_COLUMN_INDEX, SortOrder.ASCENDING)
+ );
+ sorter.setSortKeys(myDefaultSortingKeys);
+ setRowSorter(sorter);
+ setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ myCountProvider = new ReferenceCountProvider() {
+ @Override
+ public int getTotalCount(@NotNull ReferenceType ref) {
+ return (int)myCounts.get(ref).myCurrentCount;
+ }
+
+ @Override
+ public int getDiffCount(@NotNull ReferenceType ref) {
+ return (int)myCounts.get(ref).diff();
+ }
+
+ @Override
+ public int getNewInstancesCount(@NotNull ReferenceType ref) {
+ TrackerForNewInstances strategy = myParent.getStrategy(ref);
+ return strategy == null || !strategy.isReady() ? -1 : strategy.getCount();
+ }
+ };
+ }
+
+ public interface ReferenceCountProvider {
+ int getTotalCount(@NotNull ReferenceType ref);
+
+ int getDiffCount(@NotNull ReferenceType ref);
+
+ int getNewInstancesCount(@NotNull ReferenceType ref);
+ }
+
+ @Nullable
+ ReferenceType getSelectedClass() {
+ int selectedRow = getSelectedRow();
+ if (selectedRow != -1) {
+ int ix = convertRowIndexToModel(selectedRow);
+ return myItems.get(ix);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ ReferenceType getClassByName(@NotNull String name) {
+ for (ReferenceType ref : myItems) {
+ if (name.equals(ref.name())) {
+ return ref;
+ }
+ }
+
+ return null;
+ }
+
+ void setBusy(boolean value) {
+ setPaintBusy(value);
+ }
+
+ void setFilterPattern(String pattern) {
+ if (!myFilteringPattern.equals(pattern)) {
+ myFilteringPattern = pattern;
+ myMatcher = NameUtil.buildMatcher("*" + pattern).build();
+ getRowSorter().allRowsChanged();
+ if (getSelectedClass() == null && getRowCount() > 0) {
+ getSelectionModel().setSelectionInterval(0, 0);
+ }
+ }
+ }
+
+ void setFilteringByInstanceExists(boolean value) {
+ if (value != myOnlyWithInstances) {
+ myOnlyWithInstances = value;
+ getRowSorter().allRowsChanged();
+ }
+ }
+
+ void setFilteringByDiffNonZero(boolean value) {
+ if (myOnlyWithDiff != value) {
+ myOnlyWithDiff = value;
+ getRowSorter().allRowsChanged();
+ }
+ }
+
+ void setFilteringByTrackingState(boolean value) {
+ if (myOnlyTracked != value) {
+ myOnlyTracked = value;
+ getRowSorter().allRowsChanged();
+ }
+ }
+
+ void setClassesAndUpdateCounts(@NotNull List classes, @NotNull long[] counts) {
+ assert classes.size() == counts.length;
+ ReferenceType selectedClass = myModel.getSelectedClassBeforeHided();
+ int newSelectedIndex = classes.indexOf(selectedClass);
+ boolean isInitialized = !myItems.isEmpty();
+ myItems = Collections.unmodifiableList(new ArrayList<>(classes));
+
+ for (int i = 0, size = classes.size(); i < size; i++) {
+ ReferenceType ref = classes.get(i);
+ DiffValue oldValue = isInitialized && !myCounts.containsKey(ref)
+ ? new DiffValue(0, 0)
+ : myCounts.getOrDefault(ref, myUnknownValue);
+ myCounts.put(ref, oldValue.update(counts[i]));
+ }
+
+ showContent();
+
+ if (newSelectedIndex != -1 && !myModel.isHided()) {
+ int ix = convertRowIndexToView(newSelectedIndex);
+ changeSelection(ix,
+ DiffViewTableModel.CLASSNAME_COLUMN_INDEX, false, false);
+ }
+
+ getRowSorter().allRowsChanged();
+ }
+
+ void hideContent() {
+ myModel.hide();
+ }
+
+ private void showContent() {
+ myModel.show();
+ }
+
+
+ @Nullable
+ @Override
+ public Object getData(@NonNls String dataId) {
+ if (SELECTED_CLASS_KEY.is(dataId)) {
+ return getSelectedClass();
+ }
+ if (DEBUG_SESSION_KEY.is(dataId)) {
+ return myDebugSession;
+ }
+ if (NEW_INSTANCES_PROVIDER_KEY.is(dataId)) {
+ ReferenceType selectedClass = getSelectedClass();
+ if (selectedClass != null) {
+ TrackerForNewInstances strategy = myParent.getStrategy(selectedClass);
+ if (strategy != null && strategy.isReady()) {
+ List newInstances = strategy.getNewInstances();
+ return (InstancesProvider)limit -> newInstances;
+ }
+ }
+ }
+
+ if (REF_COUNT_PROVIDER_KEY.is(dataId)) {
+ return myCountProvider;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Nullable
+ private TrackingType getTrackingType(int row) {
+ ReferenceType ref = (ReferenceType)getValueAt(row, convertColumnIndexToView(DiffViewTableModel.CLASSNAME_COLUMN_INDEX));
+ return myInstancesTracker.getTrackingType(ref.name());
+ }
+
+ class DiffViewTableModel extends AbstractTableModelWithColumns {
+ final static int CLASSNAME_COLUMN_INDEX = 0;
+ final static int COUNT_COLUMN_INDEX = 1;
+ final static int DIFF_COLUMN_INDEX = 2;
+
+ // Workaround: save selection after content of classes table has been hided
+ private ReferenceType mySelectedClassWhenHided = null;
+ private boolean myIsWithContent = false;
+
+ DiffViewTableModel() {
+ super(new AbstractTableColumnDescriptor[]{
+ new AbstractTableColumnDescriptor("Class", ReferenceType.class) {
+ @Override
+ public Object getValue(int ix) {
+ return myItems.get(ix);
+ }
+ },
+ new AbstractTableColumnDescriptor("Count", Long.class) {
+ @Override
+ public Object getValue(int ix) {
+ return myCounts.getOrDefault(myItems.get(ix), myUnknownValue).myCurrentCount;
+ }
+ },
+ new AbstractTableColumnDescriptor("Diff", DiffValue.class) {
+ @Override
+ public Object getValue(int ix) {
+ return myCounts.getOrDefault(myItems.get(ix), myUnknownValue);
+ }
+ }
+ });
+ }
+
+ ReferenceType getSelectedClassBeforeHided() {
+ return mySelectedClassWhenHided;
+ }
+
+ void hide() {
+ if (myIsWithContent) {
+ mySelectedClassWhenHided = getSelectedClass();
+ myIsWithContent = false;
+ clearSelection();
+ fireTableDataChanged();
+ }
+ }
+
+ void show() {
+ if (!myIsWithContent) {
+ myIsWithContent = true;
+ getRowSorter().allRowsChanged();
+ }
+ }
+
+ boolean isHided() {
+ return !myIsWithContent;
+ }
+
+ @Override
+ public int getRowCount() {
+ return myIsWithContent ? myItems.size() : 0;
+ }
+ }
+
+ /**
+ * State transmissions for DiffValue and UnknownDiffValue
+ * unknown -> diff
+ * diff -> diff
+ *
+ * State descriptions:
+ * Unknown - instances count never executed
+ * Diff - actual value
+ */
+ private static class UnknownDiffValue extends DiffValue {
+ UnknownDiffValue() {
+ super(0);
+ }
+
+ @Override
+ boolean hasInstance() {
+ return true;
+ }
+
+ @Override
+ DiffValue update(long count) {
+ return new DiffValue(count);
+ }
+ }
+
+ private static class DiffValue implements Comparable {
+ private long myOldCount;
+
+ private long myCurrentCount;
+
+ DiffValue(long count) {
+ this(count, count);
+ }
+
+ DiffValue(long old, long current) {
+ myCurrentCount = current;
+ myOldCount = old;
+ }
+
+ DiffValue update(long count) {
+ myOldCount = myCurrentCount;
+ myCurrentCount = count;
+ return this;
+ }
+
+ boolean hasInstance() {
+ return myCurrentCount > 0;
+ }
+
+ long diff() {
+ return myCurrentCount - myOldCount;
+ }
+
+ @Override
+ public int compareTo(@NotNull DiffValue o) {
+ return Long.compare(diff(), o.diff());
+ }
+ }
+
+ private abstract class MyTableCellRenderer extends ColoredTableCellRenderer {
+ @Override
+ protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ column = convertColumnIndexToModel(column);
+ TrackingType trackingType = getTrackingType(row);
+
+ if (hasFocus) {
+ setBorder(EMPTY_BORDER);
+ }
+
+ if (trackingType != null && column == DiffViewTableModel.DIFF_COLUMN_INDEX) {
+ setIcon(AllIcons.Debugger.MemoryView.ClassTracked);
+ setTransparentIconBackground(true);
+ }
+
+ if (value != null) {
+ addText(value, isSelected, row);
+ }
+ }
+
+ protected abstract void addText(@NotNull Object value, boolean isSelected, int row);
+ }
+
+ private class MyClassColumnRenderer extends MyTableCellRenderer {
+ @Override
+ protected void addText(@NotNull Object value, boolean isSelected,
+ int row) {
+ String presentation = ((ReferenceType)value).name();
+ append(" ");
+ if (isSelected) {
+ FList textRanges = myMatcher.matchingFragments(presentation);
+ if (textRanges != null) {
+ SimpleTextAttributes attributes = new SimpleTextAttributes(getBackground(), getForeground(), null,
+ SimpleTextAttributes.STYLE_SEARCH_MATCH);
+ SpeedSearchUtil.appendColoredFragments(this, presentation, textRanges,
+ SimpleTextAttributes.REGULAR_ATTRIBUTES, attributes);
+ }
+ }
+ else {
+ append(String.format("%s", presentation), SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ }
+ }
+ }
+
+ private class MyCountColumnRenderer extends MyTableCellRenderer {
+ @Override
+ protected void addText(@NotNull Object value, boolean isSelected,
+ int row) {
+ setTextAlign(SwingConstants.RIGHT);
+ append(value.toString());
+ }
+ }
+
+ private class MyDiffColumnRenderer extends MyTableCellRenderer {
+ private final SimpleTextAttributes myClickableCellAttributes =
+ new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE, JBColor.BLUE);
+
+ @Override
+ protected void addText(@NotNull Object value, boolean isSelected,
+ int row) {
+ setTextAlign(SwingConstants.RIGHT);
+
+ ReferenceType ref = myItems.get(convertRowIndexToModel(row));
+
+ long diff = myCountProvider.getDiffCount(ref);
+ String text = String.format("%s%d", diff > 0 ? "+" : "", diff);
+
+ int newInstancesCount = myCountProvider.getNewInstancesCount(ref);
+ if (newInstancesCount >= 0) {
+ if (newInstancesCount == diff) {
+ append(text, diff == 0 ? SimpleTextAttributes.REGULAR_ATTRIBUTES : myClickableCellAttributes);
+ }
+ else {
+ append(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ if (newInstancesCount != 0) {
+ append(String.format(" (%d)", newInstancesCount), myClickableCellAttributes);
+ }
+ }
+ }
+ else {
+ append(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ }
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/ExpressionEditorWithHistory.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/ExpressionEditorWithHistory.java
new file mode 100644
index 000000000000..87dbc415d098
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/ExpressionEditorWithHistory.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.codeInsight.lookup.LookupManager;
+import com.intellij.debugger.engine.DebuggerUtils;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CustomShortcutSet;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.PopupStep;
+import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.ui.ColoredListCellRenderer;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.popup.list.ListPopupImpl;
+import com.intellij.xdebugger.XExpression;
+import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
+import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
+import com.intellij.xdebugger.impl.ui.XDebuggerExpressionEditor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.List;
+
+class ExpressionEditorWithHistory extends XDebuggerExpressionEditor {
+ private static final String HISTORY_ID_PREFIX = "filtering";
+
+ ExpressionEditorWithHistory(final @NotNull Project project,
+ final @NotNull String className,
+ final @NotNull XDebuggerEditorsProvider debuggerEditorsProvider,
+ final @Nullable Disposable parentDisposable) {
+ super(project, debuggerEditorsProvider, HISTORY_ID_PREFIX + className, null,
+ XExpressionImpl.EMPTY_EXPRESSION, false, true, true);
+
+ new AnAction("InstancesWindow.ShowHistory") {
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ showHistory();
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ e.getPresentation().setEnabled(LookupManager.getActiveLookup(getEditor()) == null);
+ }
+ }.registerCustomShortcutSet(CustomShortcutSet.fromString("DOWN"), getComponent(), parentDisposable);
+
+ new SwingWorker() {
+ @Override
+ protected Void doInBackground() throws Exception {
+ ApplicationManager.getApplication().runReadAction(() -> {
+ final PsiClass psiClass = DebuggerUtils.findClass(className,
+ project, GlobalSearchScope.allScope(project));
+ ApplicationManager.getApplication().invokeLater(() -> setContext(psiClass));
+ });
+ return null;
+ }
+ }.execute();
+ }
+
+ private void showHistory() {
+ List expressions = getRecentExpressions();
+ if (!expressions.isEmpty()) {
+ ListPopupImpl historyPopup = new ListPopupImpl(new BaseListPopupStep(null, expressions) {
+ @Override
+ public PopupStep onChosen(XExpression selectedValue, boolean finalChoice) {
+ setExpression(selectedValue);
+ requestFocusInEditor();
+ return FINAL_CHOICE;
+ }
+ }) {
+ @Override
+ protected ListCellRenderer getListElementRenderer() {
+ return new ColoredListCellRenderer() {
+ @Override
+ protected void customizeCellRenderer(@NotNull JList list, XExpression value, int index,
+ boolean selected, boolean hasFocus) {
+ append(value.getExpression(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ }
+ };
+ }
+ };
+
+ historyPopup.getList().setFont(EditorUtil.getEditorFont());
+ historyPopup.showUnderneathOf(getEditorComponent());
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/FilteringProgressView.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/FilteringProgressView.java
new file mode 100644
index 000000000000..2372b5ba01ff
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/FilteringProgressView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.debugger.memory.filtering.FilteringResult;
+import com.intellij.icons.AllIcons;
+import com.intellij.ui.JBProgressBar;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ui.components.BorderLayoutPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+class FilteringProgressView extends BorderLayoutPanel {
+ private final static String LIMIT_REACHED_LABEL = "Limit has been reached";
+ private final static String INTERRUPTED_LABEL = "Filtering has been interrupted";
+
+ private final JBProgressBar myProgressBar = new JBProgressBar();
+ private final BorderLayoutPanel myProgressPanel = new BorderLayoutPanel();
+ private final JBLabel myStopButton = new JBLabel(UIUtil.isUnderDarcula()
+ ? AllIcons.Actions.Clean : AllIcons.Actions.CleanLight);
+ private final JBLabel myProgressText = new JBLabel();
+
+ private int myProceedCount = 0;
+ private int myErrorCount = 0;
+ private int myMatchedCount = 0;
+
+ private boolean myIsInProcess = false;
+
+ @Nullable
+ private FilteringResult myCompletionReason = null;
+
+ FilteringProgressView() {
+ myStopButton.setOpaque(true);
+ myProgressPanel.addToCenter(myProgressBar);
+ myProgressPanel.addToRight(myStopButton);
+ addToTop(myProgressText);
+ addToCenter(myProgressPanel);
+ }
+
+ void addStopActionListener(@NotNull Runnable listener) {
+ myStopButton.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ listener.run();
+ }
+ });
+ }
+
+ void updateProgress(int proceedCount, int matchedCount, int errorCount) {
+ if (myIsInProcess) {
+ myProceedCount = proceedCount;
+ myMatchedCount = matchedCount;
+ myErrorCount = errorCount;
+ myProgressBar.setValue(myProceedCount);
+ myProgressText.setText(getDescription());
+ }
+ }
+
+ void start(int totalCount) {
+ reset();
+ myIsInProcess = true;
+ myProgressBar.setMinimum(0);
+ myProgressBar.setMaximum(totalCount);
+ myProgressBar.setValue(0);
+ myProgressText.setText(getDescription());
+ setProgressBarVisible(true);
+ }
+
+ void complete(@NotNull FilteringResult reason) {
+ if (!myIsInProcess) {
+ throw new IllegalStateException("First you need to start progress");
+ }
+
+ myIsInProcess = false;
+ myCompletionReason = reason;
+ myProgressText.setText(getDescription());
+ setProgressBarVisible(false);
+ }
+
+ private void reset() {
+ myIsInProcess = false;
+ myErrorCount = 0;
+ myProceedCount = 0;
+ myMatchedCount = 0;
+ myCompletionReason = null;
+ }
+
+ private void setProgressBarVisible(boolean value) {
+ myProgressPanel.setVisible(value);
+ }
+
+ private String getDescription() {
+ int total = myProgressBar.getMaximum();
+ String itemsInfo = String.format("Shown: %d / %d", myMatchedCount, total);
+ if (myIsInProcess || myCompletionReason == null) {
+ return itemsInfo;
+ }
+
+ switch (myCompletionReason) {
+ case ALL_CHECKED:
+ break;
+ case INTERRUPTED:
+ itemsInfo = String.format("%s (%s)", itemsInfo, INTERRUPTED_LABEL);
+ break;
+ case LIMIT_REACHED:
+ itemsInfo = String.format("%s (%s)", itemsInfo, LIMIT_REACHED_LABEL);
+ break;
+ }
+
+ if (!myIsInProcess && myErrorCount != 0) {
+ String errors = String.format("Errors: %d", myErrorCount);
+ return String.format("%s
%s", itemsInfo, errors);
+ }
+
+ return itemsInfo;
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesTree.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesTree.java
new file mode 100644
index 000000000000..8bd1c851482c
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesTree.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.debugger.ui.impl.watch.NodeDescriptorProvider;
+import com.intellij.debugger.ui.tree.NodeDescriptor;
+import com.intellij.debugger.ui.tree.ValueDescriptor;
+import com.intellij.openapi.actionSystem.DataKey;
+import com.intellij.openapi.project.Project;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.xdebugger.XDebugSession;
+import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
+import com.intellij.xdebugger.frame.*;
+import com.intellij.xdebugger.impl.actions.XDebuggerActions;
+import com.intellij.xdebugger.impl.frame.XValueMarkers;
+import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.sun.jdi.ObjectReference;
+import com.sun.jdi.Value;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.tree.TreePath;
+import java.util.ArrayList;
+import java.util.List;
+
+public class InstancesTree extends XDebuggerTree {
+ public static final DataKey DEBUG_SESSION_DATA_KEY = DataKey.create("InstancesTree.DebugSession");
+ private final XValueNodeImpl myRoot;
+ private final Runnable myOnRootExpandAction;
+ private final XDebugSession myDebugSession;
+ private List myChildren;
+
+ InstancesTree(@NotNull Project project,
+ @NotNull XDebugSession debugSession,
+ @NotNull XDebuggerEditorsProvider editorsProvider,
+ @Nullable XValueMarkers, ?> valueMarkers,
+ @NotNull Runnable onRootExpand) {
+ super(project, editorsProvider, null, XDebuggerActions.INSPECT_TREE_POPUP_GROUP, valueMarkers);
+ myOnRootExpandAction = onRootExpand;
+ myRoot = new XValueNodeImpl(this, null, "root", new MyRootValue());
+ myDebugSession = debugSession;
+
+ myRoot.children();
+ setRoot(myRoot, false);
+ myRoot.setLeaf(false);
+ setSelectionRow(0);
+ expandNodesOnLoad(node -> node == myRoot);
+ }
+
+ void addChildren(@NotNull XValueChildrenList children, boolean last) {
+ if (myChildren == null) {
+ myChildren = new ArrayList<>();
+ }
+
+ myChildren.add(children);
+ myRoot.addChildren(children, last);
+ }
+
+ void rebuildTree(@NotNull RebuildPolicy policy, @NotNull XDebuggerTreeState state) {
+ if (policy == RebuildPolicy.RELOAD_INSTANCES) {
+ myChildren = null;
+ }
+
+ rebuildAndRestore(state);
+ }
+
+ void rebuildTree(@NotNull RebuildPolicy policy) {
+ rebuildTree(policy, XDebuggerTreeState.saveState(this));
+ }
+
+ void setInfoMessage(@SuppressWarnings("SameParameterValue") @NotNull String text) {
+ myChildren = null;
+ myRoot.clearChildren();
+ myRoot.setMessage(text, XDebuggerUIConstants.INFORMATION_MESSAGE_ICON, SimpleTextAttributes.REGULAR_ATTRIBUTES, null);
+ }
+
+ @Nullable
+ ObjectReference getSelectedReference() {
+ TreePath selectionPath = getSelectionPath();
+ Object selectedItem = selectionPath != null ? selectionPath.getLastPathComponent() : null;
+ if (selectedItem instanceof XValueNodeImpl) {
+ XValueNodeImpl xValueNode = (XValueNodeImpl) selectedItem;
+ XValue valueContainer = xValueNode.getValueContainer();
+
+ if (valueContainer instanceof NodeDescriptorProvider) {
+ NodeDescriptor descriptor = ((NodeDescriptorProvider) valueContainer).getDescriptor();
+
+ if (descriptor instanceof ValueDescriptor) {
+ Value value = ((ValueDescriptor) descriptor).getValue();
+
+ if (value instanceof ObjectReference) return (ObjectReference) value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Object getData(@NonNls String dataId) {
+ if (DEBUG_SESSION_DATA_KEY.is(dataId)) {
+ return myDebugSession;
+ }
+
+ return super.getData(dataId);
+ }
+
+ enum RebuildPolicy {
+ RELOAD_INSTANCES, ONLY_UPDATE_LABELS
+ }
+
+ private class MyRootValue extends XValue {
+ @Override
+ public void computeChildren(@NotNull XCompositeNode node) {
+ if (myChildren == null) {
+ myOnRootExpandAction.run();
+ } else {
+ for (XValueChildrenList children : myChildren) {
+ myRoot.addChildren(children, false);
+ }
+
+ myRoot.addChildren(XValueChildrenList.EMPTY, true);
+ }
+ }
+
+ @Override
+ public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
+ node.setPresentation(null, "", "", true);
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesWindow.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesWindow.java
new file mode 100644
index 000000000000..d9b0c548c413
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesWindow.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.debugger.DebuggerManager;
+import com.intellij.debugger.engine.DebugProcessImpl;
+import com.intellij.debugger.engine.JavaValue;
+import com.intellij.debugger.engine.SuspendContextImpl;
+import com.intellij.debugger.engine.evaluation.EvaluationContext;
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
+import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
+import com.intellij.debugger.memory.filtering.FilteringResult;
+import com.intellij.debugger.memory.filtering.FilteringTask;
+import com.intellij.debugger.memory.filtering.FilteringTaskCallback;
+import com.intellij.debugger.memory.utils.*;
+import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl;
+import com.intellij.debugger.ui.impl.watch.MessageDescriptor;
+import com.intellij.debugger.ui.impl.watch.NodeManagerImpl;
+import com.intellij.debugger.ui.tree.NodeDescriptor;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.actionSystem.ex.AnActionListener;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.ui.DoubleClickListener;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBPanel;
+import com.intellij.util.ui.JBDimension;
+import com.intellij.util.ui.JBUI;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ui.update.UiNotifyConnector;
+import com.intellij.xdebugger.XDebugSession;
+import com.intellij.xdebugger.XDebugSessionListener;
+import com.intellij.xdebugger.XExpression;
+import com.intellij.xdebugger.frame.XValue;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.intellij.xdebugger.impl.XDebugSessionImpl;
+import com.intellij.xdebugger.impl.actions.XDebuggerActionBase;
+import com.intellij.xdebugger.impl.frame.XValueMarkers;
+import com.intellij.xdebugger.impl.ui.XDebuggerExpressionEditor;
+import com.intellij.xdebugger.impl.ui.tree.ValueMarkup;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState;
+import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.sun.jdi.ObjectReference;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.debugger.JavaDebuggerEditorsProvider;
+
+import javax.swing.*;
+import javax.swing.tree.TreeNode;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class InstancesWindow extends DialogWrapper {
+ private static final Logger LOG = Logger.getInstance(InstancesWindow.class);
+
+ private static final int DEFAULT_WINDOW_WIDTH = 870;
+ private static final int DEFAULT_WINDOW_HEIGHT = 400;
+ private static final int FILTERING_BUTTON_ADDITIONAL_WIDTH = 30;
+ private static final int BORDER_LAYOUT_DEFAULT_GAP = 5;
+ private static final int DEFAULT_INSTANCES_LIMIT = 500000;
+
+ private final Project myProject;
+ private final XDebugSession myDebugSession;
+ private final InstancesProvider myInstancesProvider;
+ private final String myClassName;
+ private MyInstancesView myInstancesView;
+
+ public InstancesWindow(@NotNull XDebugSession session,
+ @NotNull InstancesProvider provider,
+ @NotNull String className) {
+ super(session.getProject(), false);
+
+ myProject = session.getProject();
+ myDebugSession = session;
+ myInstancesProvider = provider;
+ myClassName = className;
+
+ addWarningMessage(null);
+ myDebugSession.addSessionListener(new XDebugSessionListener() {
+ @Override
+ public void sessionStopped() {
+ ApplicationManager.getApplication().invokeLater(() -> close(OK_EXIT_CODE));
+ }
+ }, myDisposable);
+ setModal(false);
+ init();
+ JRootPane root = myInstancesView.getRootPane();
+ root.setDefaultButton(myInstancesView.myFilterButton);
+ }
+
+ private void addWarningMessage(@Nullable String message) {
+ String warning = message == null ? "" : String.format(". Warning: %s", message);
+ setTitle(String.format("Instances of %s%s", myClassName, warning));
+ }
+
+ @NotNull
+ @Override
+ protected String getDimensionServiceKey() {
+ return "#org.jetbrains.debugger.memory.view.InstancesWindow";
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ myInstancesView = new MyInstancesView();
+ myInstancesView.setPreferredSize(
+ new JBDimension(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT));
+ return myInstancesView;
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createSouthPanel() {
+ JComponent comp = super.createSouthPanel();
+ if (comp != null) {
+ comp.add(myInstancesView.myProgress, BorderLayout.WEST);
+ }
+
+ return comp;
+ }
+
+ @NotNull
+ @Override
+ protected Action[] createActions() {
+ return new Action[]{new DialogWrapperExitAction("Close", CLOSE_EXIT_CODE)};
+ }
+
+ private class MyInstancesView extends JBPanel implements Disposable {
+ private static final int MAX_TREE_NODE_COUNT = 2000;
+ private static final int FILTERING_CHUNK_SIZE = 50;
+
+ private static final int MAX_DURATION_TO_UPDATE_TREE_SECONDS = 3;
+ private static final int FILTERING_PROGRESS_UPDATING_MIN_DELAY_MILLIS = 17; // ~ 60 fps
+
+ private final InstancesTree myInstancesTree;
+ private final XDebuggerExpressionEditor myFilterConditionEditor;
+ private final XDebugSessionListener myDebugSessionListener = new MySessionListener();
+
+ private final MyNodeManager myNodeManager = new MyNodeManager(myProject);
+
+ private final JButton myFilterButton = new JButton("Filter");
+ private final FilteringProgressView myProgress = new FilteringProgressView();
+
+ private final AnActionListener.Adapter myActionListener = new MyActionListener();
+ private final Object myFilteringTaskLock = new Object();
+
+ private boolean myIsAndroidVM = false;
+
+ private volatile MyFilteringWorker myFilteringTask = null;
+
+ MyInstancesView() {
+ super(new BorderLayout(0, JBUI.scale(BORDER_LAYOUT_DEFAULT_GAP)));
+
+ Disposer.register(InstancesWindow.this.myDisposable, this);
+ XValueMarkers, ?> markers = getValueMarkers();
+ ActionManager.getInstance().addAnActionListener(myActionListener, InstancesWindow.this.myDisposable);
+ myDebugSession.addSessionListener(myDebugSessionListener, InstancesWindow.this.myDisposable);
+ JavaDebuggerEditorsProvider editorsProvider = new JavaDebuggerEditorsProvider();
+
+ myFilterConditionEditor = new ExpressionEditorWithHistory(myProject, myClassName,
+ editorsProvider, InstancesWindow.this.myDisposable);
+
+ myFilterButton.setBorder(BorderFactory.createEmptyBorder());
+ Dimension filteringButtonSize = myFilterConditionEditor.getEditorComponent().getPreferredSize();
+ filteringButtonSize.width = JBUI.scale(FILTERING_BUTTON_ADDITIONAL_WIDTH) +
+ myFilterButton.getPreferredSize().width;
+ myFilterButton.setPreferredSize(filteringButtonSize);
+
+ JBPanel filteringPane = new JBPanel(new BorderLayout(JBUI.scale(BORDER_LAYOUT_DEFAULT_GAP), 0));
+ JBLabel sideEffectsWarning = new JBLabel("Warning: filtering may have side effects", SwingConstants.RIGHT);
+ sideEffectsWarning.setBorder(JBUI.Borders.empty(1, 0, 0, 0));
+ sideEffectsWarning.setComponentStyle(UIUtil.ComponentStyle.SMALL);
+ sideEffectsWarning.setFontColor(UIUtil.FontColor.BRIGHTER);
+
+ filteringPane.add(new JBLabel("Condition:"), BorderLayout.WEST);
+ filteringPane.add(myFilterConditionEditor.getComponent(), BorderLayout.CENTER);
+ filteringPane.add(myFilterButton, BorderLayout.EAST);
+ filteringPane.add(sideEffectsWarning, BorderLayout.SOUTH);
+
+ myProgress.addStopActionListener(this::cancelFilteringTask);
+
+ myInstancesTree = new InstancesTree(myProject, myDebugSession, editorsProvider, markers, this::updateInstances);
+
+ myFilterButton.addActionListener(e -> {
+ String expression = myFilterConditionEditor.getExpression().getExpression();
+ if (!expression.isEmpty()) {
+ myFilterConditionEditor.saveTextInHistory();
+ }
+
+ myFilterButton.setEnabled(false);
+ myInstancesTree.rebuildTree(InstancesTree.RebuildPolicy.RELOAD_INSTANCES);
+ });
+
+
+ StackFrameList list = new StackFrameList(myProject,
+ Collections.emptyList(),
+ GlobalSearchScope.allScope(myProject));
+
+ list.addListSelectionListener(e -> list.navigateToSelectedValue(false));
+ new DoubleClickListener() {
+ @Override
+ protected boolean onDoubleClick(MouseEvent event) {
+ list.navigateToSelectedValue(true);
+ return true;
+ }
+ }.installOn(list);
+
+ InstancesWithStackFrameView instancesWithStackFrame = new InstancesWithStackFrameView(myDebugSession,
+ myInstancesTree, list, myClassName);
+
+ add(filteringPane, BorderLayout.NORTH);
+ add(instancesWithStackFrame.getComponent(), BorderLayout.CENTER);
+
+ JComponent focusedComponent = myFilterConditionEditor.getEditorComponent();
+ UiNotifyConnector.doWhenFirstShown(focusedComponent, () ->
+ IdeFocusManager.findInstanceByComponent(focusedComponent)
+ .requestFocus(focusedComponent, true));
+ }
+
+ @Override
+ public void dispose() {
+ cancelFilteringTask();
+ Disposer.dispose(myInstancesTree);
+ }
+
+ private void updateInstances() {
+ DebugProcessImpl debugProcess = (DebugProcessImpl)DebuggerManager.getInstance(myProject)
+ .getDebugProcess(myDebugSession.getDebugProcess().getProcessHandler());
+
+ cancelFilteringTask();
+
+ debugProcess.getManagerThread().schedule(new DebuggerContextCommandImpl(debugProcess.getDebuggerContext()) {
+ @Override
+ public Priority getPriority() {
+ return Priority.LOWEST;
+ }
+
+ @Override
+ public void threadAction(@NotNull SuspendContextImpl suspendContext) {
+ myIsAndroidVM = AndroidUtil.isAndroidVM(debugProcess.getVirtualMachineProxy().getVirtualMachine());
+ int limit = myIsAndroidVM
+ ? AndroidUtil.ANDROID_INSTANCES_LIMIT
+ : DEFAULT_INSTANCES_LIMIT;
+ List instances = myInstancesProvider.getInstances(limit + 1);
+
+ EvaluationContextImpl evaluationContext = debugProcess
+ .getDebuggerContext().createEvaluationContext();
+
+ if (instances.size() > limit) {
+ addWarningMessage(String.format("Not all instances will be loaded (only %d)", limit));
+ instances = instances.subList(0, limit);
+ }
+
+ if (evaluationContext != null) {
+ synchronized (myFilteringTaskLock) {
+ myFilteringTask = new MyFilteringWorker(instances, myFilterConditionEditor.getExpression(), evaluationContext);
+ myFilteringTask.execute();
+ }
+ }
+ }
+ });
+ }
+
+ private void cancelFilteringTask() {
+ if (myFilteringTask != null) {
+ synchronized (myFilteringTaskLock) {
+ if (myFilteringTask != null) {
+ myFilteringTask.cancel();
+ myFilteringTask = null;
+ }
+ }
+ }
+ }
+
+ private XValueMarkers, ?> getValueMarkers() {
+ return myDebugSession instanceof XDebugSessionImpl
+ ? ((XDebugSessionImpl)myDebugSession).getValueMarkers()
+ : null;
+ }
+
+ private class MySessionListener implements XDebugSessionListener {
+ private volatile XDebuggerTreeState myTreeState = null;
+
+ @Override
+ public void sessionResumed() {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ myTreeState = XDebuggerTreeState.saveState(myInstancesTree);
+ cancelFilteringTask();
+
+ myInstancesTree.setInfoMessage(
+ "The application is running");
+ });
+ }
+
+ @Override
+ public void sessionPaused() {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ myProgress.setVisible(true);
+ myInstancesTree.rebuildTree(InstancesTree.RebuildPolicy.RELOAD_INSTANCES, myTreeState);
+ });
+ }
+ }
+
+ private class MyActionListener extends AnActionListener.Adapter {
+ @Override
+ public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
+ if (dataContext.getData(PlatformDataKeys.CONTEXT_COMPONENT) == myInstancesView.myInstancesTree &&
+ (isAddToWatchesAction(action) || isEvaluateExpressionAction(action))) {
+ final XValueNodeImpl selectedNode = XDebuggerTreeActionBase.getSelectedNode(dataContext);
+ final XValueMarkers, ?> markers = getValueMarkers();
+
+ if (markers != null && selectedNode != null) {
+
+ TreeNode currentNode = selectedNode;
+ while (!myInstancesTree.getRoot().equals(currentNode.getParent())) {
+ currentNode = currentNode.getParent();
+ }
+
+ final XValue valueContainer = ((XValueNodeImpl)currentNode).getValueContainer();
+
+ final String expression = valueContainer.getEvaluationExpression();
+ if (expression != null) {
+ markers.markValue(valueContainer,
+ new ValueMarkup(expression.replace("@", ""), new JBColor(0, 0), null));
+ }
+
+ ApplicationManager.getApplication().invokeLater(() -> myInstancesTree
+ .rebuildTree(InstancesTree.RebuildPolicy.ONLY_UPDATE_LABELS));
+ }
+ }
+ }
+
+ private boolean isAddToWatchesAction(AnAction action) {
+ final String className = action.getClass().getSimpleName();
+ return action instanceof XDebuggerTreeActionBase && className.equals("XAddToWatchesAction");
+ }
+
+ private boolean isEvaluateExpressionAction(AnAction action) {
+ final String className = action.getClass().getSimpleName();
+ return action instanceof XDebuggerActionBase && className.equals("EvaluateAction");
+ }
+ }
+
+ private class MyFilteringCallback implements FilteringTaskCallback {
+ private final ErrorsValueGroup myErrorsGroup = new ErrorsValueGroup();
+ private final EvaluationContextImpl myEvaluationContext;
+
+ private long myFilteringStartedTime;
+
+ private int myProceedCount = 0;
+ private int myMatchedCount = 0;
+ private int myErrorsCount = 0;
+
+ private long myLastTreeUpdatingTime;
+ private long myLastProgressUpdatingTime;
+
+ public MyFilteringCallback(@NotNull EvaluationContextImpl evaluationContext) {
+ myEvaluationContext = evaluationContext;
+ }
+
+ private XValueChildrenList myChildren = new XValueChildrenList();
+
+ @Override
+ public void started(int total) {
+ myFilteringStartedTime = System.nanoTime();
+ myLastTreeUpdatingTime = myFilteringStartedTime;
+ myLastProgressUpdatingTime = System.nanoTime();
+ ApplicationManager.getApplication().invokeLater(() -> myProgress.start(total));
+ }
+
+ @NotNull
+ @Override
+ public Action matched(@NotNull ObjectReference ref) {
+ final JavaValue val = new InstanceJavaValue(new InstanceValueDescriptor(myProject, ref),
+ myEvaluationContext, myNodeManager);
+ myMatchedCount++;
+ myProceedCount++;
+ myChildren.add(val);
+ updateProgress();
+ updateTree();
+
+ return myMatchedCount < MAX_TREE_NODE_COUNT ? Action.CONTINUE : Action.STOP;
+ }
+
+ @NotNull
+ @Override
+ public Action notMatched(@NotNull ObjectReference ref) {
+ myProceedCount++;
+ updateProgress();
+
+ return Action.CONTINUE;
+ }
+
+ @NotNull
+ @Override
+ public Action error(@NotNull ObjectReference ref, @NotNull String description) {
+ final JavaValue val = new InstanceJavaValue(new InstanceValueDescriptor(myProject, ref),
+ myEvaluationContext, myNodeManager);
+ myErrorsGroup.addErrorValue(description, val);
+ myProceedCount++;
+ myErrorsCount++;
+ updateProgress();
+ return Action.CONTINUE;
+ }
+
+ @Override
+ public void completed(@NotNull FilteringResult reason) {
+ if (!myErrorsGroup.isEmpty()) {
+ myChildren.addBottomGroup(myErrorsGroup);
+ }
+
+ final long duration = System.nanoTime() - myFilteringStartedTime;
+ LOG.info(String.format("Filtering completed in %d ms for %d instances",
+ TimeUnit.NANOSECONDS.toMillis(duration),
+ myProceedCount));
+
+ final int proceed = myProceedCount;
+ final int matched = myMatchedCount;
+ final int errors = myErrorsCount;
+ final XValueChildrenList childrenList = myChildren;
+ ApplicationManager.getApplication().invokeLater(() -> {
+ myProgress.updateProgress(proceed, matched, errors);
+ myInstancesTree.addChildren(childrenList, true);
+ myFilterButton.setEnabled(true);
+ myProgress.complete(reason);
+ });
+ }
+
+ private void updateProgress() {
+ final long now = System.nanoTime();
+ if (now - myLastProgressUpdatingTime > TimeUnit.MILLISECONDS.toNanos(FILTERING_PROGRESS_UPDATING_MIN_DELAY_MILLIS)) {
+ final int proceed = myProceedCount;
+ final int matched = myMatchedCount;
+ final int errors = myErrorsCount;
+ ApplicationManager.getApplication().invokeLater(() -> myProgress.updateProgress(proceed, matched, errors));
+ myLastProgressUpdatingTime = now;
+ }
+ }
+
+ private void updateTree() {
+ final long now = System.nanoTime();
+ final int newChildrenCount = myChildren.size();
+ if (newChildrenCount >= FILTERING_CHUNK_SIZE ||
+ (newChildrenCount > 0 && now - myLastTreeUpdatingTime > TimeUnit.SECONDS.toNanos(MAX_DURATION_TO_UPDATE_TREE_SECONDS))) {
+ final XValueChildrenList children = myChildren;
+ ApplicationManager.getApplication().invokeLater(() -> myInstancesTree.addChildren(children, false));
+ myChildren = new XValueChildrenList();
+ myLastTreeUpdatingTime = System.nanoTime();
+ }
+ }
+ }
+
+ private class MyFilteringWorker extends SwingWorker {
+ private final FilteringTask myTask;
+
+ MyFilteringWorker(@NotNull List refs,
+ @NotNull XExpression expression,
+ @NotNull EvaluationContextImpl evaluationContext) {
+ if (refs.size() != 0) {
+ DebugProcessImpl debugProcess =
+ (DebugProcessImpl)DebuggerManager.getInstance(myProject).getDebugProcess(myDebugSession.getDebugProcess().getProcessHandler());
+ myTask = new FilteringTask(refs.get(0).referenceType(), debugProcess, expression, refs,
+ new MyFilteringCallback(evaluationContext));
+ }
+ else {
+ myTask = null;
+ }
+ }
+
+ @Override
+ protected Void doInBackground() throws Exception {
+ if (myTask != null) {
+ myTask.run();
+ }
+
+ return null;
+ }
+
+ public void cancel() {
+ myTask.cancel();
+ super.cancel(false);
+ }
+ }
+ }
+
+ private final static class MyNodeManager extends NodeManagerImpl {
+ MyNodeManager(Project project) {
+ super(project, null);
+ }
+
+ @Override
+ public DebuggerTreeNodeImpl createNode(final NodeDescriptor descriptor, EvaluationContext evaluationContext) {
+ return new DebuggerTreeNodeImpl(null, descriptor);
+ }
+
+ @Override
+ public DebuggerTreeNodeImpl createMessageNode(MessageDescriptor descriptor) {
+ return new DebuggerTreeNodeImpl(null, descriptor);
+ }
+
+ @Override
+ public DebuggerTreeNodeImpl createMessageNode(String message) {
+ return new DebuggerTreeNodeImpl(null, new MessageDescriptor(message));
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesWithStackFrameView.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesWithStackFrameView.java
new file mode 100644
index 000000000000..234d6330f7a1
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/InstancesWithStackFrameView.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.ui.JBSplitter;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBScrollPane;
+import com.intellij.ui.components.labels.ActionLink;
+import com.intellij.xdebugger.XDebugSession;
+import com.sun.jdi.ObjectReference;
+import org.jetbrains.annotations.NotNull;
+import com.intellij.debugger.memory.component.CreationPositionTracker;
+import com.intellij.debugger.memory.component.InstancesTracker;
+import com.intellij.debugger.memory.event.InstancesTrackerListener;
+import com.intellij.debugger.memory.tracking.TrackingType;
+import com.intellij.debugger.memory.utils.StackFrameItem;
+
+import javax.swing.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+class InstancesWithStackFrameView {
+ private static final float DEFAULT_SPLITTER_PROPORTION = 0.7f;
+ private static final String EMPTY_TEXT_WHEN_ITEM_NOT_SELECTED = "Select instance to see stack frame";
+ private static final String EMPTY_TEXT_WHEN_STACK_NOT_FOUND = "No stack frame for this instance";
+ private static final String TEXT_FOR_ARRAYS = "Arrays could not be tracked";
+ private static final List EMPTY_FRAME = Collections.emptyList();
+
+ private float myHidedProportion;
+
+ private final JBSplitter mySplitter = new JBSplitter(false, DEFAULT_SPLITTER_PROPORTION);
+ private boolean myIsHided = false;
+
+ InstancesWithStackFrameView(@NotNull XDebugSession debugSession, @NotNull InstancesTree tree,
+ @NotNull StackFrameList list, String className) {
+ mySplitter.setFirstComponent(new JBScrollPane(tree));
+
+ list.setEmptyText(EMPTY_TEXT_WHEN_ITEM_NOT_SELECTED);
+ JLabel stackTraceLabel;
+ if (isArrayType(className)) {
+ stackTraceLabel = new JBLabel(TEXT_FOR_ARRAYS, SwingConstants.CENTER);
+ }
+ else {
+ ActionLink actionLink = new ActionLink("Enable tracking for new instances",
+ AllIcons.Debugger.MemoryView.ClassTracked,
+ new AnAction() {
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ InstancesTracker.getInstance(debugSession.getProject())
+ .add(className, TrackingType.CREATION);
+ }
+ });
+
+ actionLink.setHorizontalAlignment(SwingConstants.CENTER);
+ actionLink.setPaintUnderline(false);
+ stackTraceLabel = actionLink;
+ }
+
+ mySplitter.setSplitterProportionKey("InstancesWithStackFrameView.SplitterKey");
+
+ JComponent stackComponent = new JBScrollPane(list);
+
+ CreationPositionTracker tracker = CreationPositionTracker.getInstance(debugSession.getProject());
+ InstancesTracker instancesTracker = InstancesTracker.getInstance(debugSession.getProject());
+ instancesTracker.addTrackerListener(new InstancesTrackerListener() {
+ @Override
+ public void classChanged(@NotNull String name, @NotNull TrackingType type) {
+ if (Objects.equals(className, name) && type == TrackingType.CREATION) {
+ mySplitter.setSecondComponent(stackComponent);
+ }
+ }
+
+ @Override
+ public void classRemoved(@NotNull String name) {
+ if (Objects.equals(name, className)) {
+ mySplitter.setSecondComponent(stackTraceLabel);
+ }
+ }
+ }, tree);
+
+ mySplitter.setSecondComponent(instancesTracker.isTracked(className) ? stackComponent : stackTraceLabel);
+
+ mySplitter.setHonorComponentsMinimumSize(false);
+ myHidedProportion = DEFAULT_SPLITTER_PROPORTION;
+
+ tree.addTreeSelectionListener(e -> {
+ ObjectReference ref = tree.getSelectedReference();
+ if (ref != null && tracker != null) {
+ List stack = tracker.getStack(debugSession, ref);
+ if (stack != null) {
+ list.setFrame(stack);
+ if (mySplitter.getProportion() == 1.f) {
+ mySplitter.setProportion(DEFAULT_SPLITTER_PROPORTION);
+ }
+ return;
+ }
+ list.setEmptyText(EMPTY_TEXT_WHEN_STACK_NOT_FOUND);
+ }
+ else {
+ list.setEmptyText(EMPTY_TEXT_WHEN_ITEM_NOT_SELECTED);
+ }
+
+ list.setFrame(EMPTY_FRAME);
+ });
+ }
+
+ JComponent getComponent() {
+ return mySplitter;
+ }
+
+ private static boolean isArrayType(@NotNull String className) {
+ return className.contains("[]");
+ }
+
+ @SuppressWarnings("unused")
+ private void hideStackFrame() {
+ if (!myIsHided) {
+ myHidedProportion = mySplitter.getProportion();
+ mySplitter.getSecondComponent().setVisible(false);
+ mySplitter.setProportion(1.f);
+ myIsHided = true;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void showStackFrame() {
+ if (myIsHided) {
+ mySplitter.getSecondComponent().setVisible(true);
+ mySplitter.setProportion(myHidedProportion);
+ myIsHided = false;
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java
new file mode 100644
index 000000000000..7201ffd57987
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFrameList.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.debugger.engine.DebuggerUtils;
+import com.intellij.execution.filters.OpenFileHyperlinkInfo;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.ScrollType;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.ui.ColoredListCellRenderer;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.ui.components.JBList;
+import org.jetbrains.annotations.NotNull;
+import com.intellij.debugger.memory.utils.StackFrameItem;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+
+class StackFrameList extends JBList {
+ private static final char ANONYMOUS_CLASS_DELIMITER = '$';
+ private static final MyOpenFilesState myEditorState = new MyOpenFilesState();
+
+ private List myStackFrames;
+ private final Project myProject;
+ private final GlobalSearchScope myScope;
+ private final MyListModel myModel = new MyListModel();
+
+ StackFrameList(@NotNull Project project,
+ @NotNull List stack,
+ @NotNull GlobalSearchScope searchScope) {
+ super();
+
+ myStackFrames = new ArrayList<>(stack);
+ myProject = project;
+ myScope = searchScope;
+
+ setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ setModel(myModel);
+
+ setCellRenderer(new ColoredListCellRenderer() {
+ @Override
+ protected void customizeCellRenderer(@NotNull JList extends StackFrameItem> list,
+ StackFrameItem value, int index, boolean isSelected, boolean hasFocus) {
+ append(String.format("%s:%d, %s", value.methodName(), value.line(), value.className()));
+ String packageName = value.packageName();
+ if (packageName.trim().isEmpty() /*!StringUtils.isEmpty(packageName)*/) {
+ append(String.format(" (%s)", value.packageName()), SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES);
+ }
+ }
+ });
+ }
+
+ void setFrame(@NotNull List stack) {
+ myModel.update(stack);
+ }
+
+ void navigateToSelectedValue(boolean focusOnEditor) {
+ StackFrameItem selectedValue = getSelectedValue();
+ if (selectedValue != null) {
+ navigateToFrame(selectedValue, focusOnEditor);
+ }
+ }
+
+ private void navigateToFrame(@NotNull StackFrameItem frame, boolean focusOnEditor) {
+ String path = frame.path();
+ int anonymousClassDelimiterIndex = path.indexOf(ANONYMOUS_CLASS_DELIMITER);
+ int pathLength = anonymousClassDelimiterIndex > 0 ? anonymousClassDelimiterIndex : path.length();
+ path = path.substring(0, pathLength);
+ PsiClass psiClass = DebuggerUtils.findClass(path, myProject, myScope);
+ if (psiClass != null) {
+ ApplicationManager.getApplication().runReadAction(() -> {
+ PsiElement navigationElement = psiClass.getNavigationElement();
+ VirtualFile file = PsiUtilCore.getVirtualFile(navigationElement);
+
+ if (file == null) {
+ file = psiClass.getContainingFile().getVirtualFile();
+ }
+
+ OpenFileHyperlinkInfo info =
+ new OpenFileHyperlinkInfo(myProject, file, frame.line() - 1);
+ OpenFileDescriptor descriptor = info.getDescriptor();
+ if (descriptor != null) {
+ FileEditorManagerImpl manager = (FileEditorManagerImpl)FileEditorManager.getInstance(myProject);
+ VirtualFile lastFile = myEditorState.myLastOpenedFile;
+ if (myEditorState.myIsNeedToCloseLastOpenedFile && lastFile != null &&
+ manager.isFileOpen(lastFile) && !lastFile.equals(descriptor.getFile())) {
+ manager.closeFile(myEditorState.myLastOpenedFile, false, true);
+ }
+
+ descriptor.setScrollType(ScrollType.CENTER);
+ descriptor.setUseCurrentWindow(true);
+
+ if (lastFile == null || !lastFile.equals(descriptor.getFile())) {
+ myEditorState.myIsNeedToCloseLastOpenedFile = !manager.isFileOpen(descriptor.getFile());
+ }
+
+ descriptor.navigateInEditor(myProject, focusOnEditor);
+ FileEditor[] editors = manager.getEditors(descriptor.getFile());
+ if (editors.length != 0) {
+ myEditorState.myLastOpenedFile = descriptor.getFile();
+ }
+ }
+ });
+ }
+ }
+
+ private static class MyOpenFilesState {
+ VirtualFile myLastOpenedFile;
+ boolean myIsNeedToCloseLastOpenedFile;
+ }
+
+ private class MyListModel extends AbstractListModel {
+
+ void update(@NotNull List newFrame) {
+ fireIntervalRemoved(this, 0, getSize());
+ myStackFrames = newFrame;
+ fireIntervalAdded(this, 0, getSize());
+ }
+
+ @Override
+ public int getSize() {
+ return myStackFrames.size();
+ }
+
+ @Override
+ public StackFrameItem getElementAt(int index) {
+ return myStackFrames.get(index);
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFramePopup.java b/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFramePopup.java
new file mode 100644
index 000000000000..3bf5a6d0d61f
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/ui/StackFramePopup.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.psi.search.GlobalSearchScope;
+import org.jetbrains.annotations.NotNull;
+import com.intellij.debugger.memory.utils.StackFrameItem;
+
+import java.util.List;
+
+public class StackFramePopup {
+ private final Project myProject;
+ private final List myStackFrame;
+ private final GlobalSearchScope myScope;
+
+ public StackFramePopup(@NotNull Project project,
+ @NotNull List stack,
+ @NotNull GlobalSearchScope searchScope) {
+ myProject = project;
+ myStackFrame = stack;
+ myScope = searchScope;
+ }
+
+ public void show() {
+ StackFrameList list = new StackFrameList(myProject, myStackFrame, myScope);
+ list.addListSelectionListener(e -> {
+ if (!e.getValueIsAdjusting()) {
+ list.navigateToSelectedValue(false);
+ }
+ });
+
+ JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(list)
+ .setTitle("Select stack frame")
+ .setAutoSelectIfEmpty(true)
+ .setResizable(false)
+ .setItemChoosenCallback(() -> list.navigateToSelectedValue(true))
+ .createPopup();
+
+ list.setSelectedIndex(1);
+ popup.showInFocusCenter();
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/AbstractTableColumnDescriptor.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/AbstractTableColumnDescriptor.java
new file mode 100644
index 000000000000..c651a142e716
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/AbstractTableColumnDescriptor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+public abstract class AbstractTableColumnDescriptor implements AbstractTableModelWithColumns.TableColumnDescriptor {
+ private final String myName;
+ private final Class> myClass;
+ protected AbstractTableColumnDescriptor(String name, Class> elementClass) {
+ myName = name;
+ myClass = elementClass;
+ }
+
+ @Override
+ public Class> getColumnClass() {
+ return myClass;
+ }
+
+
+ @Override
+ public String getName() {
+ return myName;
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/AbstractTableModelWithColumns.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/AbstractTableModelWithColumns.java
new file mode 100644
index 000000000000..d14633c11473
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/AbstractTableModelWithColumns.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import javax.swing.table.AbstractTableModel;
+
+public abstract class AbstractTableModelWithColumns extends AbstractTableModel {
+
+ private final TableColumnDescriptor[] myColumnDescriptors;
+ public AbstractTableModelWithColumns(TableColumnDescriptor[] descriptors) {
+ myColumnDescriptors = descriptors;
+ }
+
+ @Override
+ public Class> getColumnClass(int columnIndex) {
+ return myColumnDescriptors[columnIndex].getColumnClass();
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return myColumnDescriptors[column].getName();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return myColumnDescriptors.length;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ return myColumnDescriptors[columnIndex].getValue(rowIndex);
+ }
+
+ interface TableColumnDescriptor {
+ Class> getColumnClass();
+ Object getValue(int ix);
+ String getName();
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/AndroidUtil.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/AndroidUtil.java
new file mode 100644
index 000000000000..4b22c6f9df6c
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/AndroidUtil.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.sun.jdi.VirtualMachine;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Locale;
+
+public class AndroidUtil {
+ public static final int ANDROID_COUNT_BY_CLASSES_BATCH_SIZE = 500;
+ public static final int ANDROID_INSTANCES_LIMIT = 30000;
+
+ public static boolean isAndroidVM(@NotNull VirtualMachine vm) {
+ return vm.name().toLowerCase(Locale.ENGLISH).contains("dalvik");
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/ErrorsValueGroup.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/ErrorsValueGroup.java
new file mode 100644
index 000000000000..50ba2cf355ef
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/ErrorsValueGroup.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.util.containers.HashMap;
+import com.intellij.xdebugger.frame.XCompositeNode;
+import com.intellij.xdebugger.frame.XNamedValue;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.intellij.xdebugger.frame.XValueGroup;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class ErrorsValueGroup extends XValueGroup {
+ private final Map> myErrorMessage2ValueMap = new HashMap<>();
+
+ public ErrorsValueGroup() {
+ super("Errors");
+ }
+
+ public void addErrorValue(@NotNull String message, @NotNull XNamedValue value) {
+ List lst;
+ if (!myErrorMessage2ValueMap.containsKey(message)) {
+ myErrorMessage2ValueMap.put(message, new ArrayList<>());
+ }
+
+ lst = myErrorMessage2ValueMap.get(message);
+ lst.add(value);
+ }
+
+ public boolean isEmpty() {
+ return myErrorMessage2ValueMap.isEmpty();
+ }
+
+ @Nullable
+ @Override
+ public Icon getIcon() {
+ return AllIcons.General.Error;
+ }
+
+ @Override
+ public void computeChildren(@NotNull XCompositeNode node) {
+ XValueChildrenList lst = new XValueChildrenList();
+ myErrorMessage2ValueMap.keySet().forEach(s -> lst.addTopGroup(new MyErrorsValueGroup(s)));
+ node.addChildren(lst, true);
+ }
+
+ private class MyErrorsValueGroup extends XValueGroup {
+
+ @Override
+ public void computeChildren(@NotNull XCompositeNode node) {
+ XValueChildrenList lst = new XValueChildrenList();
+ String name = getName();
+ myErrorMessage2ValueMap.get(name).forEach(lst::add);
+ node.addChildren(lst, true);
+ }
+
+ MyErrorsValueGroup(@NotNull String name) {
+ super(name);
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstanceJavaValue.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstanceJavaValue.java
new file mode 100644
index 000000000000..a7d2042bce74
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstanceJavaValue.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.debugger.engine.JavaValue;
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
+import com.intellij.debugger.ui.impl.watch.NodeManagerImpl;
+import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
+import com.sun.jdi.ObjectReference;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class InstanceJavaValue extends JavaValue {
+ public InstanceJavaValue(@NotNull ValueDescriptorImpl valueDescriptor,
+ @NotNull EvaluationContextImpl evaluationContext,
+ NodeManagerImpl nodeManager) {
+ super(null, valueDescriptor, evaluationContext, nodeManager, false);
+ }
+
+ @Nullable
+ @Override
+ public String getEvaluationExpression() {
+ ObjectReference ref = ((ObjectReference)getDescriptor().getValue());
+ return NamesUtils.getUniqueName(ref);
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return false;
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstanceValueDescriptor.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstanceValueDescriptor.java
new file mode 100644
index 000000000000..76202a68253e
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstanceValueDescriptor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.debugger.DebuggerContext;
+import com.intellij.debugger.engine.ContextUtil;
+import com.intellij.debugger.engine.evaluation.EvaluateException;
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
+import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiElementFactory;
+import com.intellij.psi.PsiExpression;
+import com.sun.jdi.ArrayReference;
+import com.sun.jdi.ObjectReference;
+import com.sun.jdi.Value;
+
+public class InstanceValueDescriptor extends ValueDescriptorImpl {
+
+ public InstanceValueDescriptor(Project project, Value value) {
+ super(project, value);
+ }
+
+ @Override
+ public String calcValueName() {
+ ObjectReference ref = ((ObjectReference) getValue());
+ if (ref instanceof ArrayReference) {
+ ArrayReference arrayReference = (ArrayReference) ref;
+ return NamesUtils.getArrayUniqueName(arrayReference);
+ }
+ return NamesUtils.getUniqueName(ref);
+ }
+
+ @Override
+ public Value calcValue(EvaluationContextImpl evaluationContext) throws EvaluateException {
+ return getValue();
+ }
+
+ @Override
+ public boolean isShowIdLabel() {
+ return false;
+ }
+
+ @Override
+ public PsiExpression getDescriptorEvaluation(DebuggerContext debuggerContext) throws EvaluateException {
+ PsiElementFactory elementFactory = JavaPsiFacade.getInstance(myProject).getElementFactory();
+ ObjectReference ref = ((ObjectReference) getValue());
+ String name = NamesUtils.getUniqueName(ref).replace("@", "");
+ String presentation = String.format("%s_DebugLabel", name);
+
+ return elementFactory.createExpressionFromText(presentation, ContextUtil.getContextElement(debuggerContext));
+ }
+}
diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/ast/JavacTreeScannerSink.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstancesProvider.java
similarity index 69%
rename from jps/jps-builders/src/org/jetbrains/jps/javac/ast/JavacTreeScannerSink.java
rename to java/debugger/impl/src/com/intellij/debugger/memory/utils/InstancesProvider.java
index 04569efe57e7..159c5884e5ff 100644
--- a/jps/jps-builders/src/org/jetbrains/jps/javac/ast/JavacTreeScannerSink.java
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/InstancesProvider.java
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.jetbrains.jps.javac.ast;
+package com.intellij.debugger.memory.utils;
-import org.jetbrains.jps.javac.ast.api.JavacDef;
-import org.jetbrains.jps.javac.ast.api.JavacRef;
+import com.sun.jdi.ObjectReference;
+import org.jetbrains.annotations.NotNull;
-interface JavacTreeScannerSink {
+import java.util.List;
- void sinkReference(JavacRef.JavacSymbolRefBase ref);
-
- void sinkDeclaration(JavacDef def);
+public interface InstancesProvider {
+ @NotNull
+ List getInstances(int limit);
}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/KeyboardUtils.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/KeyboardUtils.java
new file mode 100644
index 000000000000..0ae37a822941
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/KeyboardUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import java.awt.event.KeyEvent;
+
+public class KeyboardUtils {
+ public static boolean isEnterKey(int keyCode) {
+ return keyCode == KeyEvent.VK_ENTER;
+ }
+
+ public static boolean isUpDownKey(int keyCode) {
+ return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN;
+ }
+
+ public static boolean isBackSpace(int keyCode) {
+ return keyCode == KeyEvent.VK_BACK_SPACE;
+ }
+
+ public static boolean isCharacter(int keyCode) {
+ return KeyEvent.getKeyText(keyCode).length() == 1;
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/LowestPriorityCommand.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/LowestPriorityCommand.java
new file mode 100644
index 000000000000..757b990a826d
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/LowestPriorityCommand.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.debugger.engine.SuspendContextImpl;
+import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
+import org.jetbrains.annotations.Nullable;
+
+public class LowestPriorityCommand extends SuspendContextCommandImpl {
+ protected LowestPriorityCommand(@Nullable SuspendContextImpl suspendContext) {
+ super(suspendContext);
+ }
+
+ @Override
+ protected void commandCancelled() {
+ }
+
+ @Override
+ public Priority getPriority() {
+ return Priority.LOWEST;
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/NamesUtils.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/NamesUtils.java
new file mode 100644
index 000000000000..eec5e949ba72
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/NamesUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.sun.jdi.ArrayReference;
+import com.sun.jdi.ObjectReference;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.regex.Pattern;
+
+class NamesUtils {
+ @NotNull
+ static String getUniqueName(@NotNull ObjectReference ref) {
+ String shortName = StringUtil.getShortName(ref.referenceType().name());
+ String name = shortName.replace("[]", "Array");
+ return String.format("%s@%d", name, ref.uniqueID());
+ }
+
+ @NotNull
+ static String getArrayUniqueName(@NotNull ArrayReference ref) {
+ String shortName = StringUtil.getShortName(ref.referenceType().name());
+ int length = ref.length();
+
+ String name = shortName.replaceFirst(Pattern.quote("[]"), String.format("[%d]", length));
+ return String.format("%s@%d", name, ref.uniqueID());
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/SingleAlarmWithMutableDelay.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/SingleAlarmWithMutableDelay.java
new file mode 100644
index 000000000000..7537abbb6d38
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/SingleAlarmWithMutableDelay.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.util.Alarm;
+import org.jetbrains.annotations.NotNull;
+
+public class SingleAlarmWithMutableDelay extends Alarm {
+ private final Runnable myTask;
+ private volatile int myDelay;
+ public SingleAlarmWithMutableDelay(@NotNull Runnable task, @NotNull Disposable parentDisposable) {
+ super(ThreadToUse.POOLED_THREAD, parentDisposable);
+ myTask = task;
+ }
+
+ public void setDelay(int value) {
+ myDelay = value;
+ }
+
+ public void cancelAndRequest() {
+ if(!isDisposed()) {
+ cancelAllRequests();
+ addRequest(myTask, myDelay);
+ }
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java
new file mode 100644
index 000000000000..43093813158c
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/memory/utils/StackFrameItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.debugger.memory.utils;
+
+import com.intellij.debugger.engine.evaluation.EvaluateException;
+import com.intellij.debugger.jdi.StackFrameProxyImpl;
+import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
+import com.intellij.openapi.util.text.StringUtil;
+import com.sun.jdi.Location;
+import one.util.streamex.StreamEx;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public class StackFrameItem {
+ private final String myFilePath;
+ private final String myMethodName;
+ private final int myLineNumber;
+
+ public StackFrameItem(@NotNull String path, @NotNull String methodName, int line) {
+ myFilePath = path.replace('\\', '.');
+ myMethodName = methodName;
+ myLineNumber = line;
+ }
+
+ @NotNull
+ public String path() {
+ return myFilePath;
+ }
+
+ @NotNull
+ public String methodName() {
+ return myMethodName;
+ }
+
+ @NotNull
+ public String className() {
+ return StringUtil.getShortName(myFilePath);
+ }
+
+ @NotNull
+ public String packageName() {
+ return StringUtil.getPackageName(myFilePath);
+ }
+
+ public int line() {
+ return myLineNumber;
+ }
+
+ public static List createFrames(ThreadReferenceProxyImpl threadReferenceProxy) throws EvaluateException {
+ List stack = threadReferenceProxy == null ? null : threadReferenceProxy.frames();
+
+ if (stack != null) {
+ return StreamEx.of(stack).map(frame -> {
+ try {
+ Location loc = frame.location();
+ return new StackFrameItem(loc.declaringType().name(), loc.method().name(), loc.lineNumber());
+ }
+ catch (EvaluateException e) {
+ return null;
+ }
+ }).nonNull().toList();
+ }
+ return Collections.emptyList();
+ }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointWithHighlighter.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointWithHighlighter.java
index 3fea367d86f4..32cb304ae987 100644
--- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointWithHighlighter.java
+++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointWithHighlighter.java
@@ -42,7 +42,6 @@ import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
-import com.intellij.xdebugger.breakpoints.XBreakpointManager;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xml.CommonXmlStrings;
import com.sun.jdi.Location;
@@ -307,7 +306,7 @@ public abstract class BreakpointWithHighlighter {
@@ -338,11 +337,9 @@ public abstract class BreakpointWithHighlighter
getCategory();
- public boolean isVisible() {
+ protected boolean isVisible() {
return myVisible;
}
diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/RunToCursorBreakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/RunToCursorBreakpoint.java
index e9d1fffd1924..d872c34763a9 100644
--- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/RunToCursorBreakpoint.java
+++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/RunToCursorBreakpoint.java
@@ -15,6 +15,7 @@
*/
package com.intellij.debugger.ui.breakpoints;
+import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.openapi.project.Project;
@@ -24,6 +25,7 @@ import com.intellij.psi.PsiManager;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
+import com.sun.jdi.event.LocatableEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
@@ -102,7 +104,12 @@ public class RunToCursorBreakpoint extends LineBreakpoint>> CAPTURED_STACKS = Key.create("CAPTURED_STACKS");
+ private static final int MAX_STORED_STACKS = 1000;
+
+ public StackCapturingLineBreakpoint(Project project, DebugProcessImpl debugProcess, @NotNull SourcePosition pos) {
+ super(project, pos, false);
+ myDebugProcess = debugProcess;
+ }
+
+ @Override
+ public boolean processLocatableEvent(SuspendContextCommandImpl action, LocatableEvent event) throws EventProcessingException {
+ try {
+ SuspendContextImpl suspendContext = action.getSuspendContext();
+ if (suspendContext != null) {
+ Map