changeset 1939:a09beb448a72

Add search functionality to HistogramPanel PR3036 Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019693.html
author Anirudhan Mukundan <amukunda@redhat.com>
date Thu, 23 Jun 2016 11:17:14 -0400
parents 85d6dcfb7ef7
children 6c8f7bce641f
files vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramView.java vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsController.java vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsControllerTest.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramPanel.java vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDump.java vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDumpTest.java
diffstat 9 files changed, 202 insertions(+), 116 deletions(-) [+]
line wrap: on
line diff
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramView.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramView.java	Thu Jun 23 11:17:14 2016 -0400
@@ -38,11 +38,18 @@
 
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
 
 public abstract class HeapHistogramView extends BasicView implements UIComponent {
 
-    public abstract void display(ObjectHistogram histogram);
+    public enum HistogramAction {
+        SEARCH,
+    }
+
+    public abstract void setHistogram(ObjectHistogram histogram);
+
+    public abstract void addHistogramActionListener(ActionListener<HistogramAction> listener);
 
 }
 
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java	Thu Jun 23 11:17:14 2016 -0400
@@ -37,17 +37,22 @@
 package com.redhat.thermostat.vm.heap.analysis.client.core.internal;
 
 import java.io.IOException;
+import java.util.Collection;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.NotImplementedException;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsView;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView.HistogramAction;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider;
@@ -56,6 +61,8 @@
 import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
+import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
+import com.sun.tools.hat.internal.model.JavaHeapObject;
 
 public class HeapDumpDetailsController {
 
@@ -71,6 +78,7 @@
     private HeapTreeMapViewProvider treeMapViewProvider;
     private ObjectDetailsViewProvider objectDetailsViewProvider;
     private ObjectRootsViewProvider objectRootsViewProvider;
+    private HeapHistogramView heapHistogramView;
 
     public HeapDumpDetailsController(ApplicationService appService, HeapDumpDetailsViewProvider viewProvider, HeapHistogramViewProvider histogramProvider, HeapTreeMapViewProvider treeMapProvider, ObjectDetailsViewProvider objectDetailsProvider, ObjectRootsViewProvider objectRootsProvider) {
         this.appService = appService;
@@ -84,8 +92,22 @@
     public void setDump(HeapDump dump) {
         this.heapDump = dump;
         try {
-            HeapHistogramView heapHistogramView = histogramViewProvider.createView();
-            heapHistogramView.display(heapDump.getHistogram());
+            ObjectHistogram histogram = heapDump.getHistogram();
+            heapHistogramView = histogramViewProvider.createView();
+            heapHistogramView.setHistogram(histogram);
+            heapHistogramView.addHistogramActionListener(new ActionListener<HistogramAction>() {
+                @Override
+                public void actionPerformed(ActionEvent<HistogramAction> actionEvent) {
+                    switch (actionEvent.getActionId()) {
+                        case SEARCH:
+                            searchForObject((String) actionEvent.getPayload());
+                            break;
+                        default:
+                            throw new NotImplementedException("unknown action fired by " + actionEvent.getSource());
+                    }
+                }
+            });
+
             LocalizedString title = translator.localize(LocaleResources.HEAP_DUMP_SECTION_HISTOGRAM);
             view.addSubView(title, heapHistogramView);
             
@@ -105,6 +127,23 @@
         heapDump.searchObjects("A_RANDOM_PATTERN", 1);
     }
 
+    private void searchForObject(final String searchText) {
+        
+        appService.getApplicationExecutor().execute(new Runnable() {
+            @Override
+            public void run() {
+                Collection<String> objectIds = heapDump.wildcardSearch(searchText);
+
+                ObjectHistogram toDisplay = new ObjectHistogram();
+                for (String id : objectIds) {
+                    JavaHeapObject heapObject = heapDump.findObject(id);
+                    toDisplay.addThing(heapObject);
+                }
+                heapHistogramView.setHistogram(toDisplay);
+            }
+        });
+    }
+
     public BasicView getView() {
         return view;
     }
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsController.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsController.java	Thu Jun 23 11:17:14 2016 -0400
@@ -151,25 +151,17 @@
     }
 
     private void searchForObject() {
-        String searchText = view.getSearchText();
+        final String searchText = view.getSearchText();
         if (searchText == null || searchText.trim().isEmpty()) {
             view.setMatchingObjects(Collections.<HeapObjectUI>emptySet());
             return;
         }
 
-        final int maxResults = computeResultLimit(searchText);
-
-        if (!searchText.contains("*") && !searchText.contains("?")) {
-            searchText = "*" + searchText + "*";
-        }
-
-        final String wildcardQuery = searchText;
-
         appService.getApplicationExecutor().execute(new Runnable() {
 
             @Override
             public void run() {
-                Collection<String> objectIds = heapDump.searchObjects(wildcardQuery, maxResults);
+                Collection<String> objectIds = heapDump.wildcardSearch(searchText);
 
                 List<HeapObjectUI> toDisplay = new ArrayList<>();
                 for (String id: objectIds) {
@@ -182,10 +174,6 @@
 
     }
 
-    private int computeResultLimit(String searchText) {
-        return Math.min(1000, searchText.length() * 100);
-    }
-
     private void showObjectInfo() {
         HeapObjectUI matchingObject = view.getSelectedMatchingObject();
         if (matchingObject == null) {
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java	Thu Jun 23 11:17:14 2016 -0400
@@ -58,7 +58,8 @@
     HEAP_DUMP_HISTOGRAM_COLUMN_CLASS,
     HEAP_DUMP_HISTOGRAM_COLUMN_INSTANCES,
     HEAP_DUMP_HISTOGRAM_COLUMN_SIZE,
-    
+    HEAP_DUMP_HISTOGRAM_BROWSE_SEARCH_HINT,
+
     HEAP_DUMP_OBJECT_BROWSE_SEARCH_HINT,
     HEAP_DUMP_OBJECT_BROWSE_SEARCH_PATTERN_HELP,
     HEAP_DUMP_OBJECT_BROWSE_SEARCH_LABEL,
--- a/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties	Thu Jun 23 11:17:14 2016 -0400
@@ -18,6 +18,7 @@
 HEAP_DUMP_HISTOGRAM_COLUMN_CLASS = Class
 HEAP_DUMP_HISTOGRAM_COLUMN_INSTANCES = Instances
 HEAP_DUMP_HISTOGRAM_COLUMN_SIZE = Size (in bytes)
+HEAP_DUMP_HISTOGRAM_BROWSE_SEARCH_HINT = Search by class name (either a partial class name or a wildcard pattern)
 
 HEAP_DUMP_OBJECT_BROWSE_SEARCH_HINT = Search for objects by class name (either a partial class name or a wildcard pattern)
 HEAP_DUMP_OBJECT_BROWSE_SEARCH_PATTERN_HELP = Either a partial class name ("Button" will find "javax.swing.JButton") or a wildcard pattern ("*JButton*" will find "javax.swing.JButtonBeanInfo")
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsControllerTest.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsControllerTest.java	Thu Jun 23 11:17:14 2016 -0400
@@ -42,7 +42,6 @@
 import static org.mockito.Matchers.contains;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -57,8 +56,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.stubbing.OngoingStubbing;
 
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
@@ -122,7 +119,7 @@
         when(heapObject.getClazz()).thenReturn(heapObjectClass);
 
         HeapDump dump = mock(HeapDump.class);
-        when(dump.searchObjects(contains(SEARCH_TEXT), anyInt())).thenReturn(Arrays.asList(OBJECT_ID));
+        when(dump.wildcardSearch(contains(SEARCH_TEXT))).thenReturn(Arrays.asList(OBJECT_ID));
         when(dump.findObject(eq(OBJECT_ID))).thenReturn(heapObject);
 
         ArgumentCaptor<ActionListener> viewArgumentCaptor1 = ArgumentCaptor.forClass(ActionListener.class);
@@ -147,92 +144,6 @@
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     @Test
-    public void verifyInputConvertedIntoWildcardsIfNeeded() {
-        HeapDump heap = mock(HeapDump.class);
-        when(view.getSearchText()).thenReturn("a");
-
-        ArgumentCaptor<ActionListener> objectActionListenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
-        doNothing().when(view).addObjectActionListener(objectActionListenerCaptor.capture());
-
-        @SuppressWarnings("unused")
-        ObjectDetailsController controller = new ObjectDetailsController(appService, heap, this.objectDetailsProvider, this.objectRootsProvider);
-
-        ActionListener<ObjectAction> actionListener = objectActionListenerCaptor.getValue();
-        assertNotNull(actionListener);
-        actionListener.actionPerformed(new ActionEvent<ObjectAction>(view, ObjectAction.SEARCH));
-
-        runnableCaptor.getValue().run();
-
-        verify(heap).searchObjects("*a*", 100);
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Test
-    public void verifyWildcardInputNotConvertedIntoWildcards() {
-        HeapDump heap = mock(HeapDump.class);
-        when(view.getSearchText()).thenReturn("*a?");
-
-        ArgumentCaptor<ActionListener> objectActionListenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
-        doNothing().when(view).addObjectActionListener(objectActionListenerCaptor.capture());
-
-        @SuppressWarnings("unused")
-        ObjectDetailsController controller = new ObjectDetailsController(appService, heap, this.objectDetailsProvider, this.objectRootsProvider);
-
-        ActionListener<ObjectAction> actionListener = objectActionListenerCaptor.getValue();
-        assertNotNull(actionListener);
-        actionListener.actionPerformed(new ActionEvent<ObjectAction>(view, ObjectAction.SEARCH));
-
-        runnableCaptor.getValue().run();
-
-        verify(heap).searchObjects("*a?", 300);
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Test
-    public void verifySearchLimits() {
-
-        Object[][] limits = new Object[][] {
-            { "a",       100 },
-            { "ab",      200 },
-            { "abc",     300 },
-            { "abcd",    400 },
-            { "abcde",   500 },
-            { "abcdef",  600},
-            { "abcdefg", 700},
-            { "java.lang.Class", 1000 },
-        };
-
-        HeapDump heap = mock(HeapDump.class);
-
-        OngoingStubbing<String> ongoing = when(view.getSearchText());
-        for (int i = 0; i < limits.length; i++) {
-            ongoing = ongoing.thenReturn((String)limits[i][0]);
-        }
-
-        ArgumentCaptor<ActionListener> objectActionListenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
-        doNothing().when(view).addObjectActionListener(objectActionListenerCaptor.capture());
-
-        @SuppressWarnings("unused")
-        ObjectDetailsController controller = new ObjectDetailsController(appService, heap, this.objectDetailsProvider, this.objectRootsProvider);
-
-        ActionListener<ObjectAction> actionListener = objectActionListenerCaptor.getValue();
-        assertNotNull(actionListener);
-
-        for (int i = 0; i < limits.length; i++) {
-            actionListener.actionPerformed(new ActionEvent<ObjectAction>(view, ObjectAction.SEARCH));
-            runnableCaptor.getValue().run();
-        }
-
-        InOrder inOrder = inOrder(heap);
-        for (int i = 0; i < limits.length; i++) {
-            String text = (String) limits[i][0];
-            int times = (Integer) limits[i][1];
-            inOrder.verify(heap).searchObjects("*" + text + "*", times);
-        }
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Test
     public void verifyGettingDetailsWorks() {
         final String OBJECT_ID = "0xcafebabe";
         final String OBJECT_ID_VISIBLE = "FOO BAR";
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramPanel.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramPanel.java	Thu Jun 23 11:17:14 2016 -0400
@@ -42,14 +42,24 @@
 import java.util.List;
 
 import javax.swing.BoxLayout;
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.LayoutStyle.ComponentPlacement;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
 
 import com.redhat.thermostat.client.swing.NonEditableTableModel;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.swing.components.SearchField;
 import com.redhat.thermostat.client.swing.components.ThermostatTable;
 import com.redhat.thermostat.client.swing.components.ThermostatTableRenderer;
+import com.redhat.thermostat.client.ui.SearchProvider.SearchAction;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.utils.DescriptorConverter;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView;
@@ -57,6 +67,7 @@
 import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord;
 import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
 
+
 @SuppressWarnings("serial")
 public class HistogramPanel extends HeapHistogramView implements SwingComponent {
 
@@ -64,21 +75,80 @@
 
     private final JPanel panel;
 
-    private HeaderPanel headerPanel;
+    private ThermostatTable table;
+
+    private final ActionNotifier<HistogramAction> notifier = new ActionNotifier<>(this);
+
 
     public HistogramPanel() {
         panel = new JPanel();
         panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
 
-        headerPanel = new HeaderPanel(translator.localize(LocaleResources.HEAP_DUMP_CLASS_USAGE));
+        HeaderPanel headerPanel = createHeader();
         panel.add(headerPanel);
     }
 
+    public HeaderPanel createHeader() {
+        table = new ThermostatTable();
+        table.setDefaultRenderer(Long.class, new NiceNumberFormatter());
+        JScrollPane scrollPane = table.wrap();
+
+        final SearchField searchField = new SearchField();
+        searchField.setTooltip(translator.localize(LocaleResources.HEAP_DUMP_OBJECT_BROWSE_SEARCH_PATTERN_HELP));
+        searchField.setLabel(translator.localize(LocaleResources.HEAP_DUMP_HISTOGRAM_BROWSE_SEARCH_HINT));
+        searchField.addSearchListener(new ActionListener<SearchAction>() {
+            @Override
+            public void actionPerformed(ActionEvent<SearchAction> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case PERFORM_SEARCH:
+                        notifier.fireAction(HistogramAction.SEARCH, searchField.getSearchText());
+                        break;
+                    default:
+                        break;
+                }
+            }
+        });
+
+        JPanel displayContents = new JPanel();
+
+        GroupLayout layout = new GroupLayout(displayContents);
+        displayContents.setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(Alignment.LEADING)
+                .addGroup(layout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(layout.createParallelGroup(Alignment.LEADING)
+                        .addComponent(searchField)
+                        .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE))
+                    .addContainerGap())
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(Alignment.LEADING)
+                .addGroup(layout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(searchField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 287, Short.MAX_VALUE))
+        );
+
+        HeaderPanel headerPanel = new HeaderPanel(translator.localize(LocaleResources.HEAP_DUMP_CLASS_USAGE));
+        headerPanel.setContent(displayContents);
+        return headerPanel;
+    }
+
     @Override
-    public void display(ObjectHistogram histogram) {
-        ThermostatTable table = new ThermostatTable(new HistogramTableModel(histogram));
-        table.setDefaultRenderer(Long.class, new NiceNumberFormatter());
-        headerPanel.setContent(table.wrap());
+    public void addHistogramActionListener(ActionListener<HistogramAction> listener) {
+        notifier.addActionListener(listener);
+    }
+
+    @Override
+    public void setHistogram(final ObjectHistogram histogram) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                table.setModel(new HistogramTableModel(histogram));
+            }
+        });
     }
 
     private final class NiceNumberFormatter extends ThermostatTableRenderer {
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDump.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDump.java	Thu Jun 23 11:17:14 2016 -0400
@@ -85,6 +85,8 @@
 
     private static final Logger log = LoggingUtils.getLogger(HeapDump.class);
 
+    private static final int MAX_SEARCH_RESULTS = 1000;
+
     private final HeapInfo heapInfo;
 
     private final HeapDAO heapDAO;
@@ -225,6 +227,16 @@
         return results;
     }
 
+    public Collection<String> wildcardSearch(String searchText) {
+        int limit = Math.min(MAX_SEARCH_RESULTS, searchText.length() * 100);
+
+        String wildCardClassNamePattern = searchText;
+        if (!searchText.contains("*") && !searchText.contains("?")) {
+            wildCardClassNamePattern = "*" + searchText + "*";
+        }
+        return searchObjects(wildCardClassNamePattern, limit);
+    }
+
     public JavaHeapObject findObject(String id) {
         loadHeapDumpIfNecessary();
         return snapshot.findThing(id);
--- a/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDumpTest.java	Thu Jun 23 11:15:46 2016 -0400
+++ b/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDumpTest.java	Thu Jun 23 11:17:14 2016 -0400
@@ -41,6 +41,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
@@ -85,15 +86,17 @@
     private static final String HEAP_ID = "TEST_HEAP_ID";
 
     private HeapDump heapDump;
+    private HeapInfo heapInfo;
+    private HeapDAO heapDAO;
 
     @Before
     public void setUp() throws IOException {
         InputStream in = getClass().getResourceAsStream("/heapdump.hprof.gz");
         GZIPInputStream gzipIn = new GZIPInputStream(in);
 
-        HeapInfo heapInfo = mock(HeapInfo.class);
+        heapInfo = mock(HeapInfo.class);
         when(heapInfo.getHeapId()).thenReturn(HEAP_ID);
-        HeapDAO heapDAO = mock(HeapDAO.class);
+        heapDAO = mock(HeapDAO.class);
         when(heapDAO.getHeapDumpData(heapInfo)).thenReturn(gzipIn);
         heapDump = new HeapDump(heapInfo, heapDAO);
     }
@@ -172,5 +175,59 @@
         assertEquals("0x7d704eb20", obj.getIdString());
         assertEquals("java.util.ArrayDeque", obj.getClazz().getName());
     }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void verifyWildcardSearchInputConvertedIntoWildcardsIfNeeded() {
+        String input = "a";
+        String wildCardInput = "*" + input + "*";
+        verifyWildcardSearchObjectInput(input, wildCardInput, 100);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void verifyWildcardSearchInputNotConvertedIntoWildcards() {
+        String input1 = "a?";
+        String input2 = "a*";
+        String input3 = "*a?";
+
+        verifyWildcardSearchObjectInput(input1, input1, 200);
+        verifyWildcardSearchObjectInput(input2, input2, 200);
+        verifyWildcardSearchObjectInput(input3, input3, 300);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void verifyWildcardSearchLimits() {
+        Object[][] limits = new Object[][] {
+            { "a",       100 },
+            { "ab",      200 },
+            { "abc",     300 },
+            { "abcd",    400 },
+            { "abcde",   500 },
+            { "abcdef",  600 },
+            { "abcdefg", 700 },
+            { "java.lang.Class", 1000 },
+        };
+
+        for (Object[] limit : limits) {
+            String text = (String) limit[0];
+            int times = (Integer) limit[1];
+            verifyWildcardSearchObjectInput(text, "*" + text + "*", times);
+        }
+    }
+
+    public void verifyWildcardSearchObjectInput(String inputPattern, final String expectedPattern, final int expectedLimit) {
+        new HeapDump(heapInfo, heapDAO) {
+            @Override
+            public Collection<String> searchObjects(String pattern, int limit) {
+                assertEquals(pattern, expectedPattern);
+                assertEquals(limit, expectedLimit);
+
+                // doesn't matter
+                return null;
+            }
+        }.wildcardSearch(inputPattern);
+    }
 }