changeset 2479:81cdf24da562

[Byteman] Pre-populate x, y coordinates and filter for graphs. Reviewed-by: aazores, Alex Macdonald Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-October/021308.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Tue, 18 Oct 2016 18:34:05 +0200
parents 097cf8492c2f
children e2e5b4797c2a
files vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/LocaleResources.java vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/MetricsKeysAggregator.java vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/PredefinedKeysMapper.java vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/SwingVmBytemanView.java vm-byteman/client-swing/src/main/resources/com/redhat/thermostat/vm/byteman/client/swing/internal/strings.properties vm-byteman/client-swing/src/test/java/com/redhat/thermostat/vm/byteman/client/swing/internal/MetricsKeysAggregatorTest.java vm-byteman/client-swing/src/test/java/com/redhat/thermostat/vm/byteman/client/swing/internal/PredefinedKeysMapperTest.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/BytemanMetricDataExtractor.java vm-byteman/common/src/test/java/com/redhat/thermostat/vm/byteman/common/BytemanMetricDataExtractorTest.java
diffstat 9 files changed, 741 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/LocaleResources.java	Tue Oct 18 09:52:10 2016 -0400
+++ b/vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/LocaleResources.java	Tue Oct 18 18:34:05 2016 +0200
@@ -57,6 +57,7 @@
     IMPORT_RULE,
     FILTER,
     FILTER_VALUE_LABEL,
+    NO_FILTER_NAME,
     X_COORD,
     Y_COORD
     ;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/MetricsKeysAggregator.java	Tue Oct 18 18:34:05 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.byteman.client.swing.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
+import com.redhat.thermostat.vm.byteman.common.BytemanMetricDataExtractor;
+
+class MetricsKeysAggregator {
+    
+    static List<String> aggregate(List<BytemanMetric> metrics) {
+        BytemanMetricDataExtractor extractor = new BytemanMetricDataExtractor();
+        for (BytemanMetric m: metrics) {
+            extractor.mineMetric(m);
+        }
+        List<String> predefinedKeys = buildPredefinedKeys();
+        List<String> userDefinedKeys = extractor.getSortedKeySet();
+        // merge lists: predefined keys first, then user-defined keys
+        List<String> merged = new ArrayList<>(predefinedKeys.size() + userDefinedKeys.size());
+        merged.addAll(predefinedKeys);
+        merged.addAll(userDefinedKeys);
+        return merged;
+    }
+
+    private static List<String> buildPredefinedKeys() {
+        List<String> predefined = new ArrayList<>(PredefinedKeysMapper.PREDEFINED_KEYS.size());
+        for (String key: PredefinedKeysMapper.PREDEFINED_KEYS) {
+            predefined.add(PredefinedKeysMapper.PREDEFINED_PREFIX + key + PredefinedKeysMapper.PREDEFINED_SUFFIX);
+        }
+        return predefined;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/PredefinedKeysMapper.java	Tue Oct 18 18:34:05 2016 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.byteman.client.swing.internal;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+/**
+ * Maps and reverse maps predefined keys from the view strings to
+ * the data model strings.
+ */
+class PredefinedKeysMapper {
+    
+    static enum MapDirection {
+        VIEW_TO_MODEL,
+        MODEL_TO_VIEW
+    }
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private static final Map<String, String> PREDEFINED_KEY_MAP;
+    private static final Map<String, String> REVERSE_PREDEFINED_KEY_MAP;
+    private static final String EMPTY_STR = "";
+    
+    static final String PREDEFINED_PREFIX = "** ";
+    static final String PREDEFINED_SUFFIX = " **";
+    
+    // Sorted list of predefined keys
+    static final List<String> PREDEFINED_KEYS = Collections.unmodifiableList(Arrays.asList(
+            GraphDataset.FREQUENCY_KEY, GraphDataset.MARKER_KEY, GraphDataset.TIMESTAMP_KEY
+    ));
+    static final String NO_FILTER_ITEM = t.localize(LocaleResources.NO_FILTER_NAME).getContents();
+    
+    static {
+        PREDEFINED_KEY_MAP = new HashMap<>(PREDEFINED_KEYS.size());
+        REVERSE_PREDEFINED_KEY_MAP = new HashMap<>(PREDEFINED_KEYS.size());
+        for (String key: PREDEFINED_KEYS) {
+            String mappedValue = PREDEFINED_PREFIX + key + PREDEFINED_SUFFIX;
+            PREDEFINED_KEY_MAP.put(key, mappedValue);
+            REVERSE_PREDEFINED_KEY_MAP.put(mappedValue, key);
+        }
+    }
+    
+    String mapPredefinedKey(String value, MapDirection direction) {
+        switch (direction) {
+        case MODEL_TO_VIEW:
+            return mapValue(PREDEFINED_KEY_MAP, value);
+        case VIEW_TO_MODEL:
+            return mapValue(REVERSE_PREDEFINED_KEY_MAP, value);
+        default:
+            throw new AssertionError("Unknown direction: " + direction);
+        }
+    }
+    
+    String mapNoFilter(String value, MapDirection direction) {
+        switch (direction) {
+        case MODEL_TO_VIEW:
+            if (EMPTY_STR.equals(value)) {
+                return NO_FILTER_ITEM;
+            } else {
+                return value;
+            }
+        case VIEW_TO_MODEL:
+            if (NO_FILTER_ITEM.equals(value)) {
+                return EMPTY_STR;
+            } else {
+                return value;
+            }
+        default:
+            throw new AssertionError("Unknown direction: " + direction);
+        }
+    }
+
+    private String mapValue(Map<String, String> mapping, String value) {
+        if (value == null) {
+            return null;
+        }
+        String mappedValue = mapping.get(value);
+        if (mappedValue != null) {
+            return mappedValue;
+        } else {
+            // return unchanged
+            return value;
+        }
+    }
+}
--- a/vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/SwingVmBytemanView.java	Tue Oct 18 09:52:10 2016 -0400
+++ b/vm-byteman/client-swing/src/main/java/com/redhat/thermostat/vm/byteman/client/swing/internal/SwingVmBytemanView.java	Tue Oct 18 18:34:05 2016 +0200
@@ -54,23 +54,28 @@
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.swing.JButton;
+import javax.swing.JComboBox;
 import javax.swing.JFileChooser;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
 import javax.swing.JTabbedPane;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.JToggleButton;
-import javax.swing.JSplitPane;
 import javax.swing.SwingUtilities;
 import javax.swing.SwingWorker;
 import javax.swing.border.LineBorder;
@@ -80,6 +85,14 @@
 import javax.swing.plaf.basic.BasicSplitPaneDivider;
 import javax.swing.plaf.basic.BasicSplitPaneUI;
 
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.SymbolAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.category.CategoryDataset;
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.xy.XYDataset;
+
 import com.redhat.thermostat.client.swing.IconResource;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.ActionToggleButton;
@@ -101,16 +114,9 @@
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.byteman.client.swing.internal.GraphDataset.CoordinateType;
+import com.redhat.thermostat.vm.byteman.client.swing.internal.PredefinedKeysMapper.MapDirection;
 import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
 
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.axis.SymbolAxis;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.data.category.CategoryDataset;
-import org.jfree.data.category.DefaultCategoryDataset;
-import org.jfree.data.xy.XYDataset;
-
 public class SwingVmBytemanView extends VmBytemanView implements SwingComponent {
 
     private static final Logger logger = LoggingUtils.getLogger(SwingVmBytemanView.class);
@@ -121,6 +127,7 @@
     private static final Icon ARROW_RIGHT = IconResource.ARROW_RIGHT.getIcon();
     private static final String EMPTY_STR = "";
     private static final String BYTEMAN_CHART_LABEL = EMPTY_STR;
+    private static final PredefinedKeysMapper KEYS_MAPPER = new PredefinedKeysMapper();
     
     static final String NO_METRICS_AVAILABLE = t.localize(LocaleResources.NO_METRICS_AVAILABLE).getContents();
     
@@ -158,6 +165,12 @@
     private String filter = null;
     private String value = null;
     private String graphtype = null;
+    
+    // Graph widgets
+    private final JComboBox<String> xCombo;
+    private final JComboBox<String> yCombo;
+    private final JComboBox<String> filterCombo;
+    private final JTextField filterText;
 
     // duration over which to search for metrics
     private Duration duration = ThermostatChartPanel.DEFAULT_DATA_DISPLAY;
@@ -380,41 +393,28 @@
         // insert two labelled text fields to allow axis selection
         JLabel xlabel = new JLabel(t.localize(LocaleResources.X_COORD).getContents());
         JLabel ylabel = new JLabel(t.localize(LocaleResources.Y_COORD).getContents());
-        final JTextField xtext = new JTextField(30);
-        final JTextField ytext = new JTextField(30);
-
-        xtext.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
-
+        final List<BytemanMetric> emptyBytemanMetrics = Collections.emptyList();
+        String[] keyItems = MetricsKeysAggregator.aggregate(emptyBytemanMetrics).toArray(new String[0]);
+        xCombo = new JComboBox<>(keyItems);
+        xCombo.addActionListener(new java.awt.event.ActionListener() {
+            
             @Override
-            public void insertUpdate(DocumentEvent e)
-            {
-                xkey = xtext.getText();
-            }
-            @Override
-            public void removeUpdate(DocumentEvent e)
-            {
-                xkey = xtext.getText();
-            }
-            @Override
-            public void changedUpdate(DocumentEvent e)
-            {
-                xkey = xtext.getText();
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                @SuppressWarnings("unchecked")
+                JComboBox<String> combo = (JComboBox<String>)e.getSource();
+                String candidate = (String)combo.getSelectedItem();
+                xkey = KEYS_MAPPER.mapPredefinedKey(candidate, MapDirection.VIEW_TO_MODEL);
             }
         });
-
-        ytext.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
-
+        yCombo = new JComboBox<>(keyItems);
+        yCombo.addActionListener(new java.awt.event.ActionListener() {
+            
             @Override
-            public void insertUpdate(DocumentEvent e) {
-                ykey = ytext.getText();
-            }
-            @Override
-            public void removeUpdate(DocumentEvent e) {
-                ykey = ytext.getText();
-            }
-            @Override
-            public void changedUpdate(DocumentEvent e) {
-                ykey = ytext.getText();
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                @SuppressWarnings("unchecked")
+                JComboBox<String> combo = (JComboBox<String>)e.getSource();
+                String candidate = (String)combo.getSelectedItem();
+                ykey = KEYS_MAPPER.mapPredefinedKey(candidate, MapDirection.VIEW_TO_MODEL);
             }
         });
         // insert button to initiate graph redraw
@@ -430,39 +430,32 @@
 
         JLabel filterlabel = new JLabel(t.localize(LocaleResources.FILTER).getContents());
         JLabel valuelabel = new JLabel(t.localize(LocaleResources.FILTER_VALUE_LABEL).getContents());
-        final JTextField filterText = new JTextField(30);
-        final JTextField valueText = new JTextField(30);
-
+        String[] filterKeyVals = buildFilterKeyVals(keyItems);
+        filterCombo = new JComboBox<>(filterKeyVals);
+        filterCombo.addActionListener(new java.awt.event.ActionListener() {
+            
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                @SuppressWarnings("unchecked")
+                JComboBox<String> combo = (JComboBox<String>)e.getSource();
+                String candidate = (String)combo.getSelectedItem();
+                filter = mapFilter(candidate, MapDirection.VIEW_TO_MODEL);
+                updateFilterText(filter);
+            }
+        });
+        filterText = new JTextField(30);
         filterText.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
-
             @Override
             public void insertUpdate(DocumentEvent e) {
-                filter = filterText.getText();
+                value = filterText.getText();
             }
             @Override
             public void removeUpdate(DocumentEvent e) {
-                filter = filterText.getText();
-            }
-            @Override
-            public void changedUpdate(DocumentEvent e)
-            {
-                filter = filterText.getText();
-            }
-        });
-
-        valueText.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
-
-            @Override
-            public void insertUpdate(DocumentEvent e) {
-                value = valueText.getText();
-            }
-            @Override
-            public void removeUpdate(DocumentEvent e) {
-                value = valueText.getText();
+                value = filterText.getText();
             }
             @Override
             public void changedUpdate(DocumentEvent e) {
-                value = valueText.getText();
+                value = filterText.getText();
             }
         });
 
@@ -484,7 +477,7 @@
         graphConstraints.weighty = 0.5;
         graphConstraints.weightx = weightTextBox;
         graphConstraints.insets = spacerLeftInsets;
-        graphControlHolder.add(xtext, graphConstraints);
+        graphControlHolder.add(xCombo, graphConstraints);
         graphConstraints.fill = GridBagConstraints.HORIZONTAL;
         graphConstraints.gridx = 2;
         graphConstraints.gridy = 0;
@@ -498,7 +491,7 @@
         graphConstraints.weighty = 0.5;
         graphConstraints.weightx = weightTextBox;
         graphConstraints.insets = spacerLeftInsets;
-        graphControlHolder.add(ytext, graphConstraints);
+        graphControlHolder.add(yCombo, graphConstraints);
         graphConstraints.fill = GridBagConstraints.HORIZONTAL;
         graphConstraints.gridx = 0;
         graphConstraints.gridy = 1;
@@ -512,7 +505,7 @@
         graphConstraints.weighty = 0.5;
         graphConstraints.weightx = weightTextBox;
         graphConstraints.insets = spacerLeftInsets;
-        graphControlHolder.add(filterText, graphConstraints);
+        graphControlHolder.add(filterCombo, graphConstraints);
         graphConstraints.fill = GridBagConstraints.HORIZONTAL;
         graphConstraints.gridx = 2;
         graphConstraints.gridy = 1;
@@ -526,7 +519,7 @@
         graphConstraints.weighty = 0.5;
         graphConstraints.weightx = weightTextBox;
         graphConstraints.insets = spacerLeftInsets;
-        graphControlHolder.add(valueText, graphConstraints);
+        graphControlHolder.add(filterText, graphConstraints);
         graphConstraints.fill = GridBagConstraints.HORIZONTAL;
         graphConstraints.gridx = 4;
         graphConstraints.gridy = 1;
@@ -579,6 +572,25 @@
         mainContainer.addToolBarButton(toggleButton);
     }
 
+    /*
+     * When there is no key selected to filter by, don't enable the value
+     * input text box as this doesn't make sense.
+     */
+    private void updateFilterText(String filterValue) {
+        if (filterValue != null && !filterValue.isEmpty()) {
+            filterText.setEnabled(true);
+        } else {
+            filterText.setEnabled(false);
+        }
+    }
+
+    private String[] buildFilterKeyVals(String[] keyItems) {
+        List<String> filterKeys = new ArrayList<>(keyItems.length + 1);
+        filterKeys.add(PredefinedKeysMapper.NO_FILTER_ITEM); // default to no filter
+        filterKeys.addAll(Arrays.asList(keyItems));
+        return filterKeys.toArray(new String[] {});
+    }
+
     private void updateGraphControlPanel(double weightx, double weighty) {
         GridBagConstraints c = new GridBagConstraints();
         c.fill = GridBagConstraints.BOTH;
@@ -853,16 +865,51 @@
         final String f = filter;
         final String v = value;
         final String t = graphtype;
+        final String[] keyItems = MetricsKeysAggregator.aggregate(metrics).toArray(new String[] {});
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
+                updateComboKeyItems(keyItems, xk, yk, f);
                 GraphDataset dataset = makeGraphDataset(ms, xk, yk, f, v);
                 if (dataset != null) {
                     switchGraph(dataset, t);
                 }
             }
+
         });
     }
+    
+    private void updateComboKeyItems(String[] keyItems, String xkey, String ykey, String filter) {
+        xCombo.removeAllItems();
+        yCombo.removeAllItems();
+        filterCombo.removeAllItems();
+        filterCombo.addItem(PredefinedKeysMapper.NO_FILTER_ITEM); // allow for no filter
+        for (String key: keyItems) {
+            xCombo.addItem(key);
+            yCombo.addItem(key);
+            filterCombo.addItem(key);
+        }
+        String xSelection = KEYS_MAPPER.mapPredefinedKey(xkey, MapDirection.MODEL_TO_VIEW);
+        selectItem(xCombo, xSelection);
+        String ySelection = KEYS_MAPPER.mapPredefinedKey(ykey, MapDirection.MODEL_TO_VIEW);
+        selectItem(yCombo, ySelection);
+        String filterSelection = mapFilter(filter, MapDirection.MODEL_TO_VIEW);
+        selectItem(filterCombo, filterSelection);
+    }
+
+    private String mapFilter(String filter, MapDirection direction) {
+        String mapped = KEYS_MAPPER.mapPredefinedKey(filter, direction);
+        if (!Objects.equals(filter, mapped)) {
+            return mapped;
+        }
+        return KEYS_MAPPER.mapNoFilter(filter, direction);
+    }
+
+    private void selectItem(final JComboBox<String> combo, String selection) {
+        if (selection != null) {
+            combo.setSelectedItem(selection);
+        }
+    }
 
     private GraphDataset makeGraphDataset(List<BytemanMetric> metrics, String xkey, String ykey, String filter, String value) {
         GraphDataset dataset = new GraphDataset(metrics, xkey, ykey, filter, value);
--- a/vm-byteman/client-swing/src/main/resources/com/redhat/thermostat/vm/byteman/client/swing/internal/strings.properties	Tue Oct 18 09:52:10 2016 -0400
+++ b/vm-byteman/client-swing/src/main/resources/com/redhat/thermostat/vm/byteman/client/swing/internal/strings.properties	Tue Oct 18 18:34:05 2016 +0200
@@ -15,5 +15,6 @@
 IMPORT_RULE = Import Rule from File
 FILTER = Filter:
 FILTER_VALUE_LABEL = ==
+NO_FILTER_NAME = <No Filter>
 X_COORD = x:
 Y_COORD = y:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-swing/src/test/java/com/redhat/thermostat/vm/byteman/client/swing/internal/MetricsKeysAggregatorTest.java	Tue Oct 18 18:34:05 2016 +0200
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.byteman.client.swing.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
+
+public class MetricsKeysAggregatorTest {
+
+    @Test
+    public void testEmptyUserDefined() {
+        List<BytemanMetric> metrics = Collections.emptyList();
+        List<String> actual = MetricsKeysAggregator.aggregate(metrics);
+        assertEquals("3 predefined keys", 3, actual.size());
+        String first = PredefinedKeysMapper.PREDEFINED_PREFIX + GraphDataset.FREQUENCY_KEY + PredefinedKeysMapper.PREDEFINED_SUFFIX;
+        String second = PredefinedKeysMapper.PREDEFINED_PREFIX + GraphDataset.MARKER_KEY + PredefinedKeysMapper.PREDEFINED_SUFFIX;
+        String third = PredefinedKeysMapper.PREDEFINED_PREFIX + GraphDataset.TIMESTAMP_KEY + PredefinedKeysMapper.PREDEFINED_SUFFIX;
+        assertEquals(first, actual.get(0));
+        assertEquals(second, actual.get(1));
+        assertEquals(third, actual.get(2));
+    }
+    
+    @Test
+    public void testMergedNonEmptyUserDefined() {
+        String[] keys = new String[] {
+                "foo", "bar", "baz"
+        };
+        List<BytemanMetric> metrics = buildMetrics(keys);
+        List<String> actual = MetricsKeysAggregator.aggregate(metrics);
+        assertEquals("3 predefined + 3 user-defined keys", 6, actual.size());
+        assertEquals(keys[1], actual.get(3));
+        assertEquals(keys[2], actual.get(4));
+        assertEquals(keys[0], actual.get(5));
+    }
+
+    private List<BytemanMetric> buildMetrics(String[] keys) {
+        List<BytemanMetric> metrics = new ArrayList<>();
+        for (String key: keys) {
+            BytemanMetric m = new BytemanMetric();
+            m.setData("{\"" + key + "\": \"value\"}");
+            metrics.add(m);
+        }
+        return metrics;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-swing/src/test/java/com/redhat/thermostat/vm/byteman/client/swing/internal/PredefinedKeysMapperTest.java	Tue Oct 18 18:34:05 2016 +0200
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.byteman.client.swing.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.vm.byteman.client.swing.internal.PredefinedKeysMapper.MapDirection;
+
+public class PredefinedKeysMapperTest {
+
+    private PredefinedKeysMapper mapper;
+    
+    @Before
+    public void setup() {
+        mapper = new PredefinedKeysMapper();
+    }
+    
+    @Test
+    public void testMapKeysNull() {
+        String mapped = mapper.mapPredefinedKey(null, MapDirection.MODEL_TO_VIEW);
+        assertNull(mapped);
+        mapped = mapper.mapPredefinedKey(null, MapDirection.VIEW_TO_MODEL);
+        assertNull(mapped);
+    }
+    
+    @Test
+    public void testMapNoFilterNull() {
+        String mapped = mapper.mapNoFilter(null, MapDirection.MODEL_TO_VIEW);
+        assertNull(mapped);
+        mapped = mapper.mapNoFilter(null, MapDirection.VIEW_TO_MODEL);
+        assertNull(mapped);
+    }
+    
+    /**
+     * Only empty string and localized {@code No Filter} get mapped.
+     */
+    @Test
+    public void testMapNoFilterUntracked() {
+        String filterValUntracked = "foobar";
+        assertFalse("Precondition not met", PredefinedKeysMapper.NO_FILTER_ITEM.equals(filterValUntracked));
+        String mapped = mapper.mapNoFilter(filterValUntracked, MapDirection.MODEL_TO_VIEW);
+        assertSame(filterValUntracked, mapped);
+        mapped = mapper.mapNoFilter(filterValUntracked, MapDirection.VIEW_TO_MODEL);
+        assertSame(filterValUntracked, mapped);
+    }
+    
+    /**
+     * Only predefined keys shall map. Others should get returned verbatim.
+     */
+    @Test
+    public void testMapPredefinedKeysUntracked() {
+        String keyValUntracked = "foobar";
+        String mapped = mapper.mapPredefinedKey(keyValUntracked, MapDirection.MODEL_TO_VIEW);
+        assertSame(keyValUntracked, mapped);
+        mapped = mapper.mapPredefinedKey(keyValUntracked, MapDirection.VIEW_TO_MODEL);
+        assertSame(keyValUntracked, mapped);
+    }
+    
+    @Test
+    public void testMapFromModelPredefinedKeys() {
+        for (String keyModel: PredefinedKeysMapper.PREDEFINED_KEYS) {
+            String mapped = mapper.mapPredefinedKey(keyModel, MapDirection.MODEL_TO_VIEW);
+            String expected = PredefinedKeysMapper.PREDEFINED_PREFIX + keyModel + PredefinedKeysMapper.PREDEFINED_SUFFIX;
+            assertEquals(expected, mapped);
+        }
+    }
+    
+    @Test
+    public void testMapFromViewPredefinedKeys() {
+        for (String keyModel: PredefinedKeysMapper.PREDEFINED_KEYS) {
+            String preMapping = PredefinedKeysMapper.PREDEFINED_PREFIX + keyModel + PredefinedKeysMapper.PREDEFINED_SUFFIX;
+            String mapped = mapper.mapPredefinedKey(preMapping, MapDirection.VIEW_TO_MODEL);
+            assertEquals(keyModel, mapped);
+        }
+    }
+    
+    @Test
+    public void testMapFromModelNoFilter() {
+        String noFilterModel = "";
+        String mapped = mapper.mapNoFilter(noFilterModel, MapDirection.MODEL_TO_VIEW);
+        assertEquals(PredefinedKeysMapper.NO_FILTER_ITEM, mapped);
+    }
+    
+    @Test
+    public void testMapFromViewNoFilter() {
+        String noFilterView = PredefinedKeysMapper.NO_FILTER_ITEM;
+        String mapped = mapper.mapNoFilter(noFilterView, MapDirection.VIEW_TO_MODEL);
+        String noFilterModelExpected = "";
+        assertEquals(noFilterModelExpected, mapped);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/BytemanMetricDataExtractor.java	Tue Oct 18 18:34:05 2016 +0200
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.byteman.common;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class BytemanMetricDataExtractor {
+    
+    private final Set<String> keySet;
+
+    public BytemanMetricDataExtractor() {
+        this.keySet = new HashSet<>();
+    }
+    
+    public void mineMetric(BytemanMetric m) {
+        Objects.requireNonNull(m);
+        Map<String, Object> dataMap = m.getDataAsMap();
+        if (dataMap != null) {
+            for (String key: dataMap.keySet()) {
+                keySet.add(key);
+            }
+        }
+    }
+    
+    /**
+     * 
+     * @return A sorted list of the set of keys.
+     */
+    public List<String> getSortedKeySet() {
+        List<String> returnedList = new ArrayList<>(keySet);
+        Collections.sort(returnedList);
+        return returnedList;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/test/java/com/redhat/thermostat/vm/byteman/common/BytemanMetricDataExtractorTest.java	Tue Oct 18 18:34:05 2016 +0200
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2012-2016 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.byteman.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class BytemanMetricDataExtractorTest {
+
+    private BytemanMetricDataExtractor extractor;
+    
+    @Before
+    public void setup() {
+        extractor = new BytemanMetricDataExtractor();
+    }
+    
+    @Test
+    public void testEmptyKeySet() {
+        List<String> actual = extractor.getSortedKeySet();
+        assertTrue(actual.isEmpty());
+    }
+    
+    @Test
+    public void testAdditiveKeySet() {
+        String firstKey = "zoo";
+        BytemanMetric first = new BytemanMetric();
+        first.setData("{\"" + firstKey + "\": \"bar\"}");
+        extractor.mineMetric(first);
+        List<String> actual = extractor.getSortedKeySet();
+        assertEquals(1, actual.size());
+        assertEquals(firstKey, actual.get(0));
+        
+        BytemanMetric second = new BytemanMetric();
+        String secondKey = "secondKey";
+        second.setData("{\"" + secondKey + "\": \"baz\"}");
+        extractor.mineMetric(second);
+        actual = extractor.getSortedKeySet();
+        assertEquals(2, actual.size());
+        
+        // verify sorting
+        assertEquals(secondKey, actual.get(0));
+        assertEquals(firstKey, actual.get(1));
+    }
+    
+    @Test
+    public void testDuplicateKey() {
+        String firstKey = "zoo";
+        BytemanMetric first = new BytemanMetric();
+        first.setData("{\"" + firstKey + "\": \"bar\"}");
+        extractor.mineMetric(first);
+        List<String> actual = extractor.getSortedKeySet();
+        assertEquals(1, actual.size());
+        assertEquals(firstKey, actual.get(0));
+        
+        extractor.mineMetric(first);
+        actual = extractor.getSortedKeySet();
+        assertEquals(1, actual.size());
+        assertEquals(firstKey, actual.get(0));
+    }
+    
+    @Test
+    public void extractionDoesNotFailIfNoData() {
+        BytemanMetric m = new BytemanMetric();
+        extractor.mineMetric(m); // this shall not fail with NPE
+        List<String> actual = extractor.getSortedKeySet();
+        assertTrue(actual.isEmpty());
+    }
+    
+    @Test(expected = NullPointerException.class)
+    public void mineRequiresNonNull() {
+        extractor.mineMetric(null);
+    }
+    
+    @Test
+    public void canExtractListOfMetrics() {
+        String[] keys = new String[] {
+                "001_first", "002_second", "003_third", "004_fourth"
+        };
+        List<BytemanMetric> mList = buildMetrics(keys);
+        for (BytemanMetric metric: mList) {
+            extractor.mineMetric(metric);
+        }
+        List<String> keySet = extractor.getSortedKeySet();
+        assertEquals(4, keySet.size());
+        for (int i = 0; i < keySet.size(); i++) {
+            assertEquals(keys[0], keySet.get(0));
+        }
+        
+    }
+
+    private List<BytemanMetric> buildMetrics(String[] keys) {
+        List<BytemanMetric> list = new ArrayList<>();
+        for (String key: keys) {
+            BytemanMetric m = new BytemanMetric();
+            m.setData("{\"" + key + "\": \"" + key + "_value\"}");
+            list.add(m);
+        }
+        return list;
+    }
+}