1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.debugger.memory.ui;
4 import com.intellij.debugger.memory.component.InstancesTracker;
5 import com.intellij.debugger.memory.tracking.TrackerForNewInstances;
6 import com.intellij.debugger.memory.tracking.TrackingType;
7 import com.intellij.debugger.memory.utils.AbstractTableColumnDescriptor;
8 import com.intellij.debugger.memory.utils.AbstractTableModelWithColumns;
9 import com.intellij.debugger.memory.utils.InstancesProvider;
10 import com.intellij.icons.AllIcons;
11 import com.intellij.openapi.Disposable;
12 import com.intellij.openapi.actionSystem.DataKey;
13 import com.intellij.openapi.actionSystem.DataProvider;
14 import com.intellij.openapi.application.ApplicationManager;
15 import com.intellij.openapi.util.TextRange;
16 import com.intellij.psi.codeStyle.MinusculeMatcher;
17 import com.intellij.psi.codeStyle.NameUtil;
18 import com.intellij.ui.ColoredTableCellRenderer;
19 import com.intellij.ui.JBColor;
20 import com.intellij.ui.SimpleTextAttributes;
21 import com.intellij.ui.speedSearch.SpeedSearchUtil;
22 import com.intellij.ui.table.JBTable;
23 import com.intellij.util.containers.FList;
24 import com.intellij.util.ui.JBDimension;
25 import com.intellij.util.ui.JBUI;
26 import com.sun.jdi.ObjectReference;
27 import com.sun.jdi.ReferenceType;
28 import org.jetbrains.annotations.NonNls;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
33 import javax.swing.border.Border;
34 import javax.swing.table.TableColumn;
35 import javax.swing.table.TableColumnModel;
36 import javax.swing.table.TableRowSorter;
38 import java.awt.event.MouseAdapter;
39 import java.awt.event.MouseEvent;
40 import java.awt.event.MouseListener;
42 import java.util.List;
43 import java.util.concurrent.ConcurrentHashMap;
45 public class ClassesTable extends JBTable implements DataProvider, Disposable {
46 public static final DataKey<ReferenceType> SELECTED_CLASS_KEY = DataKey.create("ClassesTable.SelectedClass");
47 public static final DataKey<InstancesProvider> NEW_INSTANCES_PROVIDER_KEY =
48 DataKey.create("ClassesTable.NewInstances");
49 public static final DataKey<ReferenceCountProvider> REF_COUNT_PROVIDER_KEY =
50 DataKey.create("ClassesTable.ReferenceCountProvider");
52 private static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder();
53 private static final JBColor CLICKABLE_COLOR = new JBColor(new Color(250, 251, 252), new Color(62, 66, 69));
54 private static final String DEFAULT_EMPTY_TEXT = "Nothing to show";
56 private static final int CLASSES_COLUMN_PREFERRED_WIDTH = 250;
57 private static final int COUNT_COLUMN_MIN_WIDTH = 80;
58 private static final int DIFF_COLUMN_MIN_WIDTH = 80;
59 private static final UnknownDiffValue UNKNOWN_VALUE = new UnknownDiffValue();
61 private final DiffViewTableModel myModel = new DiffViewTableModel();
62 private final Map<ReferenceType, DiffValue> myCounts = new ConcurrentHashMap<>();
63 private final InstancesTracker myInstancesTracker;
64 private final ClassesFilteredView myParent;
65 private final ReferenceCountProvider myCountProvider;
67 private boolean myOnlyWithDiff;
68 private boolean myOnlyTracked;
69 private boolean myOnlyWithInstances;
70 private MinusculeMatcher myMatcher = NameUtil.buildMatcher("*").build();
71 private String myFilteringPattern = "";
73 private volatile List<ReferenceType> myItems = Collections.unmodifiableList(new ArrayList<>());
74 private boolean myIsShowCounts = true;
75 private MouseListener myMouseListener = null;
77 public ClassesTable(@NotNull InstancesTracker tracker, @NotNull ClassesFilteredView parent, boolean onlyWithDiff,
78 boolean onlyWithInstances,
79 boolean onlyTracked) {
82 myOnlyWithDiff = onlyWithDiff;
83 myOnlyWithInstances = onlyWithInstances;
84 myOnlyTracked = onlyTracked;
85 myInstancesTracker = tracker;
88 final TableColumnModel columnModel = getColumnModel();
89 TableColumn classesColumn = columnModel.getColumn(DiffViewTableModel.CLASSNAME_COLUMN_INDEX);
90 TableColumn countColumn = columnModel.getColumn(DiffViewTableModel.COUNT_COLUMN_INDEX);
91 TableColumn diffColumn = columnModel.getColumn(DiffViewTableModel.DIFF_COLUMN_INDEX);
93 setAutoResizeMode(AUTO_RESIZE_SUBSEQUENT_COLUMNS);
94 classesColumn.setPreferredWidth(JBUI.scale(CLASSES_COLUMN_PREFERRED_WIDTH));
96 countColumn.setMinWidth(JBUI.scale(COUNT_COLUMN_MIN_WIDTH));
98 diffColumn.setMinWidth(JBUI.scale(DIFF_COLUMN_MIN_WIDTH));
101 setIntercellSpacing(new JBDimension(0, 0));
103 setDefaultRenderer(ReferenceType.class, new MyClassColumnRenderer());
104 setDefaultRenderer(Long.class, new MyCountColumnRenderer());
105 setDefaultRenderer(DiffValue.class, new MyDiffColumnRenderer());
107 TableRowSorter<DiffViewTableModel> sorter = new TableRowSorter<>(myModel);
108 sorter.setRowFilter(new RowFilter<DiffViewTableModel, Integer>() {
110 public boolean include(Entry<? extends DiffViewTableModel, ? extends Integer> entry) {
111 int ix = entry.getIdentifier();
112 ReferenceType ref = myItems.get(ix);
113 DiffValue diff = myCounts.getOrDefault(ref, UNKNOWN_VALUE);
115 boolean isFilteringOptionsRefused = myOnlyWithDiff && diff.diff() == 0
116 || myOnlyWithInstances && !diff.hasInstance()
117 || myOnlyTracked && myParent.getStrategy(ref) == null;
118 return !(isFilteringOptionsRefused) && myMatcher.matches(ref.name());
122 List<RowSorter.SortKey> myDefaultSortingKeys = Arrays.asList(
123 new RowSorter.SortKey(DiffViewTableModel.DIFF_COLUMN_INDEX, SortOrder.DESCENDING),
124 new RowSorter.SortKey(DiffViewTableModel.COUNT_COLUMN_INDEX, SortOrder.DESCENDING),
125 new RowSorter.SortKey(DiffViewTableModel.CLASSNAME_COLUMN_INDEX, SortOrder.ASCENDING)
127 sorter.setSortKeys(myDefaultSortingKeys);
128 setRowSorter(sorter);
129 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
131 myCountProvider = new ReferenceCountProvider() {
133 public int getTotalCount(@NotNull ReferenceType ref) {
134 return (int)myCounts.get(ref).myCurrentCount;
138 public int getDiffCount(@NotNull ReferenceType ref) {
139 return (int)myCounts.get(ref).diff();
143 public int getNewInstancesCount(@NotNull ReferenceType ref) {
144 TrackerForNewInstances strategy = myParent.getStrategy(ref);
145 return strategy == null || !strategy.isReady() ? -1 : strategy.getCount();
150 public interface ReferenceCountProvider {
152 int getTotalCount(@NotNull ReferenceType ref);
154 int getDiffCount(@NotNull ReferenceType ref);
156 int getNewInstancesCount(@NotNull ReferenceType ref);
160 ReferenceType getSelectedClass() {
161 int selectedRow = getSelectedRow();
162 if (selectedRow != -1) {
163 int ix = convertRowIndexToModel(selectedRow);
164 return myItems.get(ix);
171 ReferenceType getClassByName(@NotNull String name) {
172 for (ReferenceType ref : myItems) {
173 if (name.equals(ref.name())) {
181 boolean isInClickableMode() {
182 return myMouseListener != null;
185 void makeClickable(@NotNull String text, @NotNull Runnable onClick) {
186 releaseMouseListener();
187 getEmptyText().setText(text);
189 if (!ApplicationManager.getApplication().isUnitTestMode() && getMousePosition() != null) {
190 setBackground(CLICKABLE_COLOR);
193 myMouseListener = new MouseAdapter() {
195 public void mouseClicked(MouseEvent e) {
197 releaseMouseListener();
201 public void mouseEntered(MouseEvent e) {
202 setBackground(CLICKABLE_COLOR);
206 public void mouseExited(MouseEvent e) {
207 setBackground(JBColor.background());
211 addMouseListener(myMouseListener);
212 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
215 void exitClickableMode() {
216 releaseMouseListener();
217 getEmptyText().setText(DEFAULT_EMPTY_TEXT);
220 private void releaseMouseListener() {
221 ApplicationManager.getApplication().assertIsDispatchThread();
222 if (isInClickableMode()) {
223 removeMouseListener(myMouseListener);
224 myMouseListener = null;
225 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
226 setBackground(JBColor.background());
230 void setBusy(boolean value) {
234 void setFilterPattern(String pattern) {
235 if (!myFilteringPattern.equals(pattern)) {
236 myFilteringPattern = pattern;
237 myMatcher = NameUtil.buildMatcher("*" + pattern).build();
238 fireTableDataChanged();
239 if (getSelectedClass() == null && getRowCount() > 0) {
240 getSelectionModel().setSelectionInterval(0, 0);
245 void setFilteringByInstanceExists(boolean value) {
246 if (value != myOnlyWithInstances) {
247 myOnlyWithInstances = value;
248 fireTableDataChanged();
252 void setFilteringByDiffNonZero(boolean value) {
253 if (myOnlyWithDiff != value) {
254 myOnlyWithDiff = value;
255 fireTableDataChanged();
259 void setFilteringByTrackingState(boolean value) {
260 if (myOnlyTracked != value) {
261 myOnlyTracked = value;
262 fireTableDataChanged();
266 public void updateClassesOnly(@NotNull List<ReferenceType> classes) {
267 myIsShowCounts = false;
268 final LinkedHashMap<ReferenceType, Long> class2Count = new LinkedHashMap<>();
269 classes.forEach(x -> class2Count.put(x, 0L));
270 updateCountsInternal(class2Count);
273 public void updateContent(@NotNull Map<ReferenceType, Long> class2Count) {
274 myIsShowCounts = true;
275 updateCountsInternal(class2Count);
278 void hideContent(@NotNull String emptyText) {
279 releaseMouseListener();
280 getEmptyText().setText(emptyText);
285 private void showContent() {
289 private void updateCountsInternal(@NotNull Map<ReferenceType, Long> class2Count) {
290 releaseMouseListener();
291 getEmptyText().setText(DEFAULT_EMPTY_TEXT);
293 final ReferenceType selectedClass = myModel.getSelectedClassBeforeHide();
294 int newSelectedIndex = -1;
295 final boolean isInitialized = !myItems.isEmpty();
296 myItems = Collections.unmodifiableList(new ArrayList<>(class2Count.keySet()));
299 for (final ReferenceType ref : class2Count.keySet()) {
300 if (ref.equals(selectedClass)) {
301 newSelectedIndex = i;
304 final DiffValue oldValue = isInitialized && !myCounts.containsKey(ref)
305 ? new DiffValue(0, 0)
306 : myCounts.getOrDefault(ref, UNKNOWN_VALUE);
307 myCounts.put(ref, oldValue.update(class2Count.get(ref)));
314 if (newSelectedIndex != -1 && !myModel.isHidden()) {
315 final int ix = convertRowIndexToView(newSelectedIndex);
317 DiffViewTableModel.CLASSNAME_COLUMN_INDEX, false, false);
320 fireTableDataChanged();
325 public Object getData(@NonNls String dataId) {
326 if (SELECTED_CLASS_KEY.is(dataId)) {
327 return getSelectedClass();
329 if (NEW_INSTANCES_PROVIDER_KEY.is(dataId)) {
330 ReferenceType selectedClass = getSelectedClass();
331 if (selectedClass != null) {
332 TrackerForNewInstances strategy = myParent.getStrategy(selectedClass);
333 if (strategy != null && strategy.isReady()) {
334 List<ObjectReference> newInstances = strategy.getNewInstances();
335 return (InstancesProvider)limit -> newInstances;
340 if (REF_COUNT_PROVIDER_KEY.is(dataId)) {
341 return myCountProvider;
347 public void clean(@NotNull String emptyText) {
349 releaseMouseListener();
350 getEmptyText().setText(emptyText);
351 myItems = Collections.emptyList();
353 myModel.mySelectedClassWhenHidden = null;
354 fireTableDataChanged();
358 public void dispose() {
359 ApplicationManager.getApplication().invokeLater(() -> clean(""));
363 private TrackingType getTrackingType(int row) {
364 ReferenceType ref = (ReferenceType)getValueAt(row, convertColumnIndexToView(DiffViewTableModel.CLASSNAME_COLUMN_INDEX));
365 return myInstancesTracker.getTrackingType(ref.name());
368 private void fireTableDataChanged() {
369 myModel.fireTableDataChanged();
372 class DiffViewTableModel extends AbstractTableModelWithColumns {
373 final static int CLASSNAME_COLUMN_INDEX = 0;
374 final static int COUNT_COLUMN_INDEX = 1;
375 final static int DIFF_COLUMN_INDEX = 2;
377 // Workaround: save selection after content of classes table has been hided
378 private ReferenceType mySelectedClassWhenHidden = null;
379 private boolean myIsWithContent = false;
381 DiffViewTableModel() {
382 super(new AbstractTableColumnDescriptor[]{
383 new AbstractTableColumnDescriptor("Class", ReferenceType.class) {
385 public Object getValue(int ix) {
386 return myItems.get(ix);
389 new AbstractTableColumnDescriptor("Count", Long.class) {
391 public Object getValue(int ix) {
392 return myCounts.getOrDefault(myItems.get(ix), UNKNOWN_VALUE).myCurrentCount;
395 new AbstractTableColumnDescriptor("Diff", DiffValue.class) {
397 public Object getValue(int ix) {
398 return myCounts.getOrDefault(myItems.get(ix), UNKNOWN_VALUE);
404 ReferenceType getSelectedClassBeforeHide() {
405 return mySelectedClassWhenHidden;
409 if (myIsWithContent) {
410 mySelectedClassWhenHidden = getSelectedClass();
411 myIsWithContent = false;
413 fireTableDataChanged();
418 if (!myIsWithContent) {
419 myIsWithContent = true;
420 fireTableDataChanged();
425 return !myIsWithContent;
429 public int getRowCount() {
430 return myIsWithContent ? myItems.size() : 0;
435 * State transmissions for DiffValue and UnknownDiffValue
439 * State descriptions:
440 * Unknown - instances count never executed
441 * Diff - actual value
443 private static class UnknownDiffValue extends DiffValue {
449 boolean hasInstance() {
454 DiffValue update(long count) {
455 return new DiffValue(count);
459 private static class DiffValue implements Comparable<DiffValue> {
460 private long myOldCount;
462 private long myCurrentCount;
464 DiffValue(long count) {
468 DiffValue(long old, long current) {
469 myCurrentCount = current;
473 DiffValue update(long count) {
474 myOldCount = myCurrentCount;
475 myCurrentCount = count;
479 boolean hasInstance() {
480 return myCurrentCount > 0;
484 return myCurrentCount - myOldCount;
488 public int compareTo(@NotNull DiffValue o) {
489 return Long.compare(diff(), o.diff());
493 private abstract static class MyTableCellRenderer extends ColoredTableCellRenderer {
495 protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean isSelected,
496 boolean hasFocus, int row, int column) {
499 setBorder(EMPTY_BORDER);
503 addText(value, isSelected, row);
507 protected abstract void addText(@NotNull Object value, boolean isSelected, int row);
510 private class MyClassColumnRenderer extends MyTableCellRenderer {
512 protected void addText(@NotNull Object value, boolean isSelected,
514 String presentation = ((ReferenceType)value).name();
517 FList<TextRange> textRanges = myMatcher.matchingFragments(presentation);
518 if (textRanges != null) {
519 SimpleTextAttributes attributes = new SimpleTextAttributes(getBackground(), getForeground(), null,
520 SimpleTextAttributes.STYLE_SEARCH_MATCH);
521 SpeedSearchUtil.appendColoredFragments(this, presentation, textRanges,
522 SimpleTextAttributes.REGULAR_ATTRIBUTES, attributes);
526 append(String.format("%s", presentation), SimpleTextAttributes.REGULAR_ATTRIBUTES);
531 private abstract class MyNumericRenderer extends MyTableCellRenderer {
533 protected void addText(@NotNull Object value, boolean isSelected, int row) {
534 if (myIsShowCounts) {
535 setTextAlign(SwingConstants.RIGHT);
536 appendText(value, row);
540 abstract void appendText(@NotNull Object value, int row);
543 private class MyCountColumnRenderer extends MyNumericRenderer {
545 void appendText(@NotNull Object value, int row) {
546 append(value.toString());
550 private class MyDiffColumnRenderer extends MyNumericRenderer {
551 private final SimpleTextAttributes myClickableCellAttributes =
552 new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE, JBColor.BLUE);
555 void appendText(@NotNull Object value, int row) {
556 TrackingType trackingType = getTrackingType(row);
557 if (trackingType != null) {
558 setIcon(AllIcons.Debugger.MemoryView.ClassTracked);
559 setTransparentIconBackground(true);
562 ReferenceType ref = myItems.get(convertRowIndexToModel(row));
564 long diff = myCountProvider.getDiffCount(ref);
565 String text = String.format("%s%d", diff > 0 ? "+" : "", diff);
567 int newInstancesCount = myCountProvider.getNewInstancesCount(ref);
568 if (newInstancesCount >= 0) {
569 if (newInstancesCount == diff) {
570 append(text, diff == 0 ? SimpleTextAttributes.REGULAR_ATTRIBUTES : myClickableCellAttributes);
573 append(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);
574 if (newInstancesCount != 0) {
575 append(String.format(" (%d)", newInstancesCount), myClickableCellAttributes);
580 append(text, SimpleTextAttributes.REGULAR_ATTRIBUTES);