changeset 1988:44ded819f2bf

HistogramConverter refactor PR3059 Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019844.html Original-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-November/016870.html
author James Aziz <jaziz@redhat.com>
date Wed, 29 Jun 2016 12:23:11 -0400
parents 0abb727993f5
children 10e0b0fe84ef
files client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/AbstractTreeAssembler.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeAssembler.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeConverter.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/AbstractTreeAssemblerTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeConverterTest.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverter.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectHistogramTreeAssembler.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/SwingHeapTreeMapView.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverterTest.java vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectHistogramTreeAssemblerTest.java
diffstat 10 files changed, 760 insertions(+), 368 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/AbstractTreeAssembler.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012-2015 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.client.swing.components.experimental;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class AbstractTreeAssembler<T> implements TreeAssembler<T> {
+
+    @Override
+    public abstract void buildTree(T data, TreeMapNode root);
+
+    public static TreeMapNode processRecord(String name, String token, TreeMapNode lastProcessed) {
+        while (!name.equals("")) {
+
+            String nodeId = name.split(Pattern.quote(token))[0];
+
+            TreeMapNode child = searchNode(lastProcessed.getChildren(), nodeId);
+            if (child == null) {
+                child = new TreeMapNode(nodeId, 0);
+                lastProcessed.addChild(child);
+            }
+
+            lastProcessed = child;
+
+            name = name.substring(nodeId.length());
+            if (name.startsWith(".")) {
+                name = name.substring(1);
+            }
+        }
+        return lastProcessed;
+    }
+
+    public static TreeMapNode searchNode(List<TreeMapNode> nodes, String nodeId) {
+        for (TreeMapNode node : nodes) {
+            if (node.getLabel().equals(nodeId)) {
+                return node;
+            }
+        }
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeAssembler.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2015 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.client.swing.components.experimental;
+
+public interface TreeAssembler<T> {
+
+    void buildTree(T data, TreeMapNode root);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeConverter.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2015 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.client.swing.components.experimental;
+
+import java.util.List;
+
+public class TreeConverter {
+
+    /**
+     * This method builds a tree map model of the {@link T} data provided, using the
+     * specified {@link TreeAssembler} assembler.
+     *
+     * @return the root of the resulting tree
+     */
+    public static <T> TreeMapNode convertToTreeMap(T data, TreeAssembler<T> assembler) {
+        TreeMapNode root = new TreeMapNode("", 0);
+
+        assembler.buildTree(data, root);
+        // calculates weights for inner nodes
+        fillWeights(root);
+        // collapse nodes with only one child
+        packTree(root);
+        return root;
+    }
+
+    /**
+     * This method calculates the real weights using a bottom-up traversal.  The weight of a
+     * parent node is the sum of its children's weights.
+     *
+     * Package-private for testing purposes.
+     *
+     * @param node the root of the subtree
+     * @return the real weight of the root
+     */
+    static double fillWeights(TreeMapNode node) {
+        if (node.getChildren().size() == 0) {
+            return node.getRealWeight();
+        }
+
+        double sum = 0;
+        for (TreeMapNode child : node.getChildren()) {
+            sum += fillWeights(child);
+        }
+        node.setRealWeight(sum);
+        return node.getRealWeight();
+    }
+
+    /**
+     * This method allows the collapse of a series of nodes, each with at most one child, into a
+     * single node placed at the root of the series.
+     *
+     * Package-private for testing purposes.
+     *
+     * @param node the root of the subtree
+     */
+    static void packTree(TreeMapNode node) {
+        List<TreeMapNode> children = node.getChildren();
+        if (children.size() == 1) {
+            TreeMapNode child = children.get(0);
+            node.setLabel(node.getLabel() + "." + child.getLabel());
+            node.setChildren(child.getChildren());
+            packTree(node);
+        } else {
+            for (TreeMapNode child : children) {
+                packTree(child);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/AbstractTreeAssemblerTest.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012-2015 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.client.swing.components.experimental;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AbstractTreeAssemblerTest {
+
+    final private String SOME_ROOT_LABEL = "root";
+    final private double SOME_ROOT_WEIGHT = 25.0;
+    final private String SPLIT_TOKEN = ".";
+
+    private TreeMapNode root;
+
+    @Before
+    public void setup() {
+        root = new TreeMapNode(SOME_ROOT_LABEL, SOME_ROOT_WEIGHT);
+    }
+
+    @Test
+    public void testProcessRecord() {
+        String class1 = "com.Class1";
+        AbstractTreeAssembler.processRecord(class1, SPLIT_TOKEN, root);
+
+        List<TreeMapNode> children = root.getChildren();
+        assertEquals(1, children.size());
+        assertEquals("com", children.get(0).getLabel());
+
+        children = children.get(0).getChildren();
+        assertEquals(1, children.size());
+        assertEquals("Class1", children.get(0).getLabel());
+        assertEquals(0, children.get(0).getChildren().size());
+
+        String class2 = "com.Class2";
+        AbstractTreeAssembler.processRecord(class2, SPLIT_TOKEN, root);
+
+        children = root.getChildren();
+        assertEquals(1, children.size());
+        assertEquals("com", children.get(0).getLabel());
+
+        children = children.get(0).getChildren();
+        assertEquals(2, children.size());
+        assertNotNull(AbstractTreeAssembler.searchNode(children, "Class1"));
+        assertNotNull(AbstractTreeAssembler.searchNode(children, "Class2"));
+        assertEquals(0, children.get(0).getChildren().size());
+        assertEquals(0, children.get(1).getChildren().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeConverterTest.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2012-2015 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.client.swing.components.experimental;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.Pair;
+
+public class TreeConverterTest {
+
+    final private String SPLIT_TOKEN = ".";
+    final private String SOME_ROOT_LABEL = "root";
+    final private double SOME_ROOT_WEIGHT = 25.0;
+    final private double DELTA = 0.01;
+    final private double SOME_WEIGHT = 3.14;
+
+    private ArrayList<Pair<String, Double>> data;
+    private TreeMapNode root;
+
+    @Before
+    public void setUp() {
+        /*
+         * This is the classes structure used for the test and built using
+         * the data. Nodes are compacted at the point of branching
+         * so for example the node relative to "example2.package1.Class1" is
+         * still just one node "example2.package1.Class1" but the node relative
+         * to "com.example1.package1.Class1" branches at "com" so becomes the
+         * common parent node "com" with children "example1" and "example2",
+         * and so forth:
+         *
+         *                ________com_______                    java
+         *               /                  \                     |
+         *        __example1__            example2              lang
+         *       /            \             |                     |
+         *    package1      package2      package1              Object
+         *    /     \           |            |
+         * Class1  Class2     Class3       Class4
+         *
+         *
+         *
+         *
+         * Expected tree after conversion:
+         *
+         *                           ________________________root_______________
+         *                          /                                           \
+         *                ________com_______                                java.lang.Object
+         *               /                  \
+         *         __example1__           example2.package1.Class4
+         *        /            \
+         *    package1      package2.Class3
+         *    /     \
+         *  Class1  Class2
+         *
+         */
+        final String[] classes = {
+                "com.example1.package1.Class1",
+                "com.example1.package1.Class2",
+                "com.example1.package2.Class3",
+                "com.example2.package1.Class4",
+                "example2.package1.Class1",
+                "java.lang.Object",
+                "repeat1.repeat1.RepeatClass",
+                "repeat2.repeat2A.RepeatClass",
+                "repeat2.repeat2B.RepeatClass",
+        };
+
+        data = new ArrayList<Pair<String, Double>>();
+
+        for (int i = 0; i < classes.length; i++) {
+            data.add(new Pair<String, Double>(classes[i], new Double(i)));
+        }
+
+        root = new TreeMapNode(SOME_ROOT_LABEL, SOME_ROOT_WEIGHT);
+    }
+
+    @Test
+    public void testConvertToTreeMap() {
+        AbstractTreeAssembler<ArrayList<Pair<String, Double>>> assembler =
+                new AbstractTreeAssembler<ArrayList<Pair<String, Double>>>() {
+
+            @Override
+            public void buildTree(ArrayList<Pair<String, Double>> data, TreeMapNode root) {
+                for (Pair<String, Double> element: data) {
+                    TreeMapNode lastProcessed = processRecord(element.getFirst(), SPLIT_TOKEN, root);
+                    lastProcessed.setRealWeight(element.getSecond());
+                }
+            }
+        };
+        TreeMapNode tree = TreeConverter.convertToTreeMap(data, assembler);
+
+        List<TreeMapNode> nodes = tree.getChildren();
+        assertEquals(5, nodes.size());
+
+        TreeMapNode node = AbstractTreeAssembler.searchNode(nodes, "com");
+        assertNotNull(node);
+
+        List<TreeMapNode> nodesFromCom = node.getChildren();
+
+        // example1 and example2
+        assertEquals(2, nodesFromCom.size());
+        node = AbstractTreeAssembler.searchNode(nodesFromCom, "example1");
+        assertNotNull(node);
+
+        // package2.Class3 and package1
+        assertEquals(2, node.getChildren().size());
+
+        node = AbstractTreeAssembler.searchNode(node.getChildren(), "package2.Class3");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        node = AbstractTreeAssembler.searchNode(nodes, "java.lang.Object");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        node = AbstractTreeAssembler.searchNode(nodes, "example2.package1.Class1");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        // now on to the  "repeat1.repeat1.RepeatClass" bunch
+        node = AbstractTreeAssembler.searchNode(nodes, "repeat1.repeat1.RepeatClass");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        node = AbstractTreeAssembler.searchNode(nodes, "repeat2");
+        assertNotNull(node);
+        assertEquals(2, node.getChildren().size());
+
+        List<TreeMapNode> nodesFromRepeat2 = node.getChildren();
+        node = AbstractTreeAssembler.searchNode(nodesFromRepeat2, "repeat2A.RepeatClass");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        node = AbstractTreeAssembler.searchNode(nodesFromRepeat2, "repeat2B.RepeatClass");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+    }
+
+    @Test
+    public void testFillWeights() {
+        assertEquals(SOME_ROOT_WEIGHT, TreeConverter.fillWeights(root), DELTA);
+        TreeMapNode parent = new TreeMapNode("childA", SOME_WEIGHT);
+        root.addChild(parent);
+
+        final double weightAA = 5.0;
+        final double weightAB = 10.0;
+        parent.addChild(new TreeMapNode("childAA", weightAA));
+        parent.addChild(new TreeMapNode("childAB", weightAB));
+
+        assertEquals(SOME_ROOT_WEIGHT, root.getRealWeight(), DELTA);
+        TreeConverter.fillWeights(root);
+        assertEquals(weightAA + weightAB, root.getRealWeight(), DELTA);
+
+        List<TreeMapNode> children = parent.getChildren();
+
+        TreeMapNode child = AbstractTreeAssembler.searchNode(children, "childAA");
+        assertNotNull(child);
+        assertEquals(weightAA, child.getRealWeight(), DELTA);
+
+        child = AbstractTreeAssembler.searchNode(children, "childAB");
+        assertNotNull(child);
+        assertEquals(weightAB, child.getRealWeight(), DELTA);
+    }
+
+    @Test
+    public void testPackTree() {
+        TreeMapNode child = new TreeMapNode("childA", SOME_WEIGHT);
+        root.addChild(child);
+        child.addChild(new TreeMapNode("childAA", SOME_WEIGHT));
+        child.addChild(new TreeMapNode("childAB", SOME_WEIGHT));
+        child = new TreeMapNode("childB", SOME_WEIGHT);
+        root.addChild(child);
+        TreeMapNode grandchild = new TreeMapNode("childBA", SOME_WEIGHT);
+        child.addChild(grandchild);
+        grandchild.addChild(new TreeMapNode("childBAA", SOME_WEIGHT));
+
+        TreeConverter.packTree(root);
+
+        assertEquals(0, child.getChildren().size());
+        assertEquals("childB.childBA.childBAA", child.getLabel());
+
+        child = AbstractTreeAssembler.searchNode(root.getChildren(), "childA");
+        assertNotNull(child);
+        assertNotNull(AbstractTreeAssembler.searchNode(child.getChildren(), "childAA"));
+        assertNotNull(AbstractTreeAssembler.searchNode(child.getChildren(), "childAB"));
+    }
+}
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverter.java	Wed Jun 29 12:23:10 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-/*
- * 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.heap.analysis.client.swing.internal;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.redhat.thermostat.client.swing.components.experimental.TreeMapNode;
-import com.redhat.thermostat.common.utils.DescriptorConverter;
-import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord;
-import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
-
-/**
- * This class provides statics function to create a {@link TreeMapNode} tree
- * from an ObjectHistrogram.
- */
-public class HistogramConverter {
-    
-    /**
-     * The splitter regular expression to split records' className
-     */
-    private static final String SPLIT_REG_EXP = "\\."; //escaped dot
-    
-    /**
-     * Key used to put into nodes the <i>number of instances</i> information 
-     * stored in histogram records.
-     */
-    private static final String NUMBER_OF = "Number Of Instances";  
-    
-    /**
-     * Call this method to create the full TreeMapNode object corresponding to
-     * the {@link ObjectHistogram} histogram given in input.
-     * @param histrogram the histogram to represent as TreeMapNode
-     * @return the resulting tree
-     */
-    public static TreeMapNode convertToTreeMap(ObjectHistogram histrogram) {
-        TreeMapNode root = new TreeMapNode("", 0);
-        
-        List<HistogramRecord> records = new ArrayList<>();
-        records.addAll(histrogram.getHistogram());
-
-        // build the tree from the histogram object
-        processRecords(records, root);
-        // calculates weights for inner nodes
-        fillWeights(root);
-        // collapse nodes with only one child 
-        packTree(root);
-        return root;
-    }
-    
-    /**
-     * This method is responsible for building correctly the histogram 
-     * corresponding tree. For each histogram record, a tree branch is created
-     * but only leaves node have a weight value.
-     * Furthermore, additional information are added to the nodes' map.
-     * 
-     * @param records {@list} of HistogramRecord used to build the tree.
-     * @param root the tree's root.
-     */
-    private static void processRecords(List<HistogramRecord> records, TreeMapNode root) {
-        
-        for (int i = 0; i < records.size(); i++) {
-            
-            TreeMapNode lastProcessed = root;
-            String className = records.get(i).getClassname();
-            
-            // if className is a primitive type it is converted with its full name
-            className = DescriptorConverter.toJavaType(className);
-
-            while (!className.equals("")) {
-               
-                String nodeId = className.split(SPLIT_REG_EXP)[0];
-                
-                TreeMapNode child = searchNode(lastProcessed, nodeId);
-                if (child == null) {
-                    child = new TreeMapNode(nodeId, 0);
-                    lastProcessed.addChild(child);
-                }
-                
-                lastProcessed = child;
-
-                className = className.substring(nodeId.length());
-                if (className.startsWith(".")) {
-                    className = className.substring(1);
-                }
-                // removes semicolon from leaves
-                if (className.endsWith(";")) {
-                    className.replace(";", "");
-                }
-            }
-            
-            // at this point lastProcessed references to a leaf
-            lastProcessed.setRealWeight(records.get(i).getTotalSize());
-            lastProcessed.addInfo(NUMBER_OF, 
-                    Long.toString(records.get(i).getNumberOf()));
-        }
-    }
-
-    private static TreeMapNode searchNode(TreeMapNode root, String nodeId) {
-        List<TreeMapNode> nodes = root.getChildren();
-        for (TreeMapNode node : nodes) {
-            if (node.getLabel().equals(nodeId)) {
-                return node;
-            }
-        }
-        return null;
-    }
-    
-    /**
-     * This method calcs the real weights using a bottom-up traversal. From leaves, 
-     * weights are passed to parent nodem which will have as weight the sum of
-     * the children's weights.
-     * 
-     * @param node the subtree's root from which start to calc weights.
-     * @return the node's real weight.
-     */
-    private static double fillWeights(TreeMapNode node) {
-        if (node.getChildren().size() == 0) {
-            return node.getRealWeight();
-        }
-
-        double sum = 0;
-        for (TreeMapNode child : node.getChildren()) {
-            sum += fillWeights(child);
-        }
-        node.setRealWeight(sum);
-        return node.getRealWeight();
-    }
-
-    /**
-     * This method allows to collapse nodes which have only one child.
-     * E.g. nodes labeled <i>com</i> and <i>example</i> are collapsed in the 
-     * parent node, which will have as label <i>com.example</i>.
-     * @param node the subree's root from which start packing.
-     */
-    private static void packTree(TreeMapNode node) {
-        if (node.getChildren().size() == 1) {
-            TreeMapNode child = node.getChildren().get(0);
-            node.setLabel(node.getLabel() + "." + child.getLabel());
-            node.setChildren(child.getChildren());
-            packTree(node);
-        } else {
-            for (TreeMapNode child : node.getChildren()) {
-                packTree(child);
-            }
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectHistogramTreeAssembler.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012-2015 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.heap.analysis.client.swing.internal;
+
+import com.redhat.thermostat.client.swing.components.experimental.AbstractTreeAssembler;
+import com.redhat.thermostat.client.swing.components.experimental.TreeMapNode;
+import com.redhat.thermostat.common.utils.DescriptorConverter;
+import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord;
+import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
+
+public class ObjectHistogramTreeAssembler extends AbstractTreeAssembler<ObjectHistogram> {
+
+    /**
+     * Key used to put into nodes the <i>number of instances</i> information
+     * stored in histogram records.
+     */
+    private static final String NUMBER_OF = "Number Of Instances";
+
+    @Override
+    public void buildTree(ObjectHistogram histogram, TreeMapNode root) {
+        for (HistogramRecord record : histogram.getHistogram()) {
+            String className = record.getClassname();
+
+            // if className is a primitive type it is converted with its full name
+            className = DescriptorConverter.toJavaType(className);
+            TreeMapNode lastProcessed = processRecord(className, ".", root);
+
+            // at this point lastProcessed references to a leaf
+            lastProcessed.setRealWeight(record.getTotalSize());
+            lastProcessed.addInfo(NUMBER_OF, Long.toString(record.getNumberOf()));
+        }
+    }
+}
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/SwingHeapTreeMapView.java	Wed Jun 29 12:23:10 2016 -0400
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/SwingHeapTreeMapView.java	Wed Jun 29 12:23:11 2016 -0400
@@ -43,6 +43,7 @@
 import javax.swing.SwingUtilities;
 
 import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.experimental.TreeConverter;
 import com.redhat.thermostat.client.swing.components.experimental.TreeMapComponent;
 import com.redhat.thermostat.client.swing.components.experimental.TreeMapNode;
 import com.redhat.thermostat.client.swing.components.experimental.TreeMapToolbar;
@@ -68,7 +69,8 @@
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                TreeMapNode model = HistogramConverter.convertToTreeMap(histogram);
+                TreeMapNode model = TreeConverter.convertToTreeMap(
+                        histogram, new ObjectHistogramTreeAssembler());
                 treeMap.setModel(model);
                 panel.removeAll();
                 panel.add(treeMap, BorderLayout.CENTER);
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverterTest.java	Wed Jun 29 12:23:10 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/*
- * 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.heap.analysis.client.swing.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertNotNull;
-
-import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.client.swing.components.experimental.TreeMapNode;
-import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
-import com.sun.tools.hat.internal.model.JavaClass;
-import com.sun.tools.hat.internal.model.JavaHeapObject;
-
-public class HistogramConverterTest {
-    
-    ObjectHistogram histrogram;
-
-    @Before
-    public void setUp() throws Exception {
-        /*
-         * This is the classes structure used for the test and built using
-         * histogram. Nodes are compacted at the point of branching
-         * so for example the node relative to "example2.package1.Class1" is
-         * still just one node "example2.package1.Class1" but the node relative
-         * to "com.example1.package1.Class1" branches at "com" so becomes the
-         * common parent node "com" with children "example1" and "example2",
-         * and so fort:
-         * 
-         *                ________com_______                    java
-         *               /                  \                     |
-         *         __example1__           example2              lang
-         *       /            \             |                    |
-         *    package1      package2      package1              Object
-         *    /     \           |            |   
-         * Class1  Class2     Class3       Class4         
-         * 
-         * 
-         * 
-         * 
-         * Expected tree after conversion:
-         * 
-         *                           ________________________root_______________
-         *                          /                                           \
-         *                ________com_______                                java.lang.Object 
-         *               /                  \                               
-         *         __example1__           example2.package1.Class4        
-         *        /            \
-         *    package1      package2.Class3
-         *    /     \
-         *  Class1  Class2   
-         *      
-         */
-        final String[] classes = {
-                    "com.example1.package1.Class1", 
-                    "com.example1.package1.Class2",
-                    "com.example1.package2.Class3",
-                    "com.example2.package1.Class4",
-                    "example2.package1.Class1",
-                    "java.lang.Object",
-                    "repeat1.repeat1.RepeatClass",
-                    "repeat2.repeat2A.RepeatClass",
-                    "repeat2.repeat2B.RepeatClass",
-                };
-        
-        histrogram = new ObjectHistogram();
-        
-        for (int i = 0; i < classes.length; i++) {
-            final String className = classes[i];
-            
-            histrogram.addThing(new JavaHeapObject() {
-                public int getSize() {
-                    return 0;
-                }
-                
-                public long getId() {
-                    return 0;
-                }
-                
-                public JavaClass getClazz() {
-                    return new JavaClass(className, 0, 0, 0, 0, null, null, 0);
-                }
-            });
-        }
-    }
-    
-    private static TreeMapNode searchNode(List<TreeMapNode> nodes, String nodeId) {
-        for (TreeMapNode node : nodes) {
-            if (node.getLabel().equals(nodeId)) {
-                return node;
-            }
-        }
-        return null;
-    }
-    
-    @Test
-    public void testconvertToTreeMap() {
-        TreeMapNode tree = HistogramConverter.convertToTreeMap(histrogram);
-
-        List<TreeMapNode> nodes = tree.getChildren();
-        assertEquals(5, nodes.size());
-        
-        TreeMapNode node = searchNode(nodes, "com");
-        assertNotNull(node);
-
-        List<TreeMapNode> nodesFromCom = node.getChildren();
-        
-        // example1 and example2
-        assertEquals(2, nodesFromCom.size());
-        node = searchNode(nodesFromCom, "example1");
-        assertNotNull(node);
-        
-        // package2.Class3 and package1
-        assertEquals(2, node.getChildren().size());
-        
-        node = searchNode(node.getChildren(), "package2.Class3");
-        assertNotNull(node);
-        assertTrue(node.getChildren().isEmpty());
-
-        node = searchNode(nodes, "java.lang.Object");
-        assertNotNull(node);
-        assertTrue(node.getChildren().isEmpty());
-
-        node = searchNode(nodes, "example2.package1.Class1");
-        assertNotNull(node);
-        assertTrue(node.getChildren().isEmpty());
-        
-        // now on to the  "repeat1.repeat1.RepeatClass" bunch
-        node = searchNode(nodes, "repeat1.repeat1.RepeatClass");
-        assertNotNull(node);
-        assertTrue(node.getChildren().isEmpty());
-        
-        node = searchNode(nodes, "repeat2");
-        assertNotNull(node);
-        assertEquals(2, node.getChildren().size());
-        
-        List<TreeMapNode> nodesFromRepeat2 = node.getChildren();
-        node = searchNode(nodesFromRepeat2, "repeat2A.RepeatClass");
-        assertNotNull(node);
-        assertTrue(node.getChildren().isEmpty());
-        
-        node = searchNode(nodesFromRepeat2, "repeat2B.RepeatClass");
-        assertNotNull(node);
-        assertTrue(node.getChildren().isEmpty());
-    }
-
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ObjectHistogramTreeAssemblerTest.java	Wed Jun 29 12:23:11 2016 -0400
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012-2015 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.heap.analysis.client.swing.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.client.swing.components.experimental.AbstractTreeAssembler;
+import com.redhat.thermostat.client.swing.components.experimental.TreeMapNode;
+import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
+import com.sun.tools.hat.internal.model.JavaClass;
+import com.sun.tools.hat.internal.model.JavaHeapObject;
+
+public class ObjectHistogramTreeAssemblerTest {
+
+    private final String ROOT_LABEL = "root";
+    private final double ROOT_WEIGHT = 10.0;
+
+    private ObjectHistogram histogram;
+    private TreeMapNode root;
+
+    @Before
+    public void setup() {
+
+        final String[] classes = {
+                "com.example1.Class1",
+                "com.example1.Class2",
+                "com.example2",
+                "java.lang.Object",
+        };
+
+        histogram = new ObjectHistogram();
+        for (int i = 0; i < classes.length; i++) {
+            final String className = classes[i];
+
+            histogram.addThing(new JavaHeapObject() {
+                public int getSize() {
+                    return 0;
+                }
+
+                public long getId() {
+                    return 0;
+                }
+
+                public JavaClass getClazz() {
+                    return new JavaClass(className, 0, 0, 0, 0, null, null, 0);
+                }
+            });
+        }
+
+        root = new TreeMapNode(ROOT_LABEL, ROOT_WEIGHT);
+    }
+
+    @Test
+    public void testBuildTree() {
+        /*
+         * This is a visualization of the expected resulting tree.
+         *
+         *                          _____________root____________
+         *                         /                             \
+         *                ________com_______                    java
+         *               /                  \                     |
+         *        __example1__            example2              lang
+         *       /            \                                   |
+         *    Class1         Class2                            Object
+         *
+         */
+
+        ObjectHistogramTreeAssembler assembler = new ObjectHistogramTreeAssembler();
+        assembler.buildTree(histogram, root);
+
+        List<TreeMapNode> children = root.getChildren();
+        assertEquals(2, children.size());
+
+        TreeMapNode node = AbstractTreeAssembler.searchNode(children, "java");
+        assertNotNull(node);
+        children = node.getChildren();
+        assertEquals(1, children.size());
+        node = AbstractTreeAssembler.searchNode(children, "lang");
+        assertNotNull(node);
+        children = node.getChildren();
+        assertEquals(1, children.size());
+        node = AbstractTreeAssembler.searchNode(children, "Object");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        children = root.getChildren();
+        node = AbstractTreeAssembler.searchNode(children, "com");
+        assertNotNull(node);
+        children = node.getChildren();
+        assertEquals(2, children.size());
+
+        node = AbstractTreeAssembler.searchNode(children, "example2");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        node = AbstractTreeAssembler.searchNode(root.getChildren(), "com");
+        assertNotNull(node);
+        node = AbstractTreeAssembler.searchNode(node.getChildren(), "example1");
+        assertNotNull(node);
+        children = node.getChildren();
+        assertEquals(2, children.size());
+
+        node = AbstractTreeAssembler.searchNode(children, "Class1");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+
+        node = AbstractTreeAssembler.searchNode(children, "Class2");
+        assertNotNull(node);
+        assertTrue(node.getChildren().isEmpty());
+    }
+
+}