weighted tree: Add test for the WeightedTree class

And update the javadoc for this class

Change-Id: I35cec4dec6b56584a536b4759eeafa15ac042225
Signed-off-by: Geneviève Bastien <gbastien+lttng@versatic.net>
Reviewed-on: https://git.eclipse.org/r/153137
Tested-by: CI Bot
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core.tests/src/org/eclipse/tracecompass/incubator/analysis/core/tests/weighted/WeightedTreeTest.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core.tests/src/org/eclipse/tracecompass/incubator/analysis/core/tests/weighted/WeightedTreeTest.java
new file mode 100644
index 0000000..5dd9887
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core.tests/src/org/eclipse/tracecompass/incubator/analysis/core/tests/weighted/WeightedTreeTest.java
@@ -0,0 +1,313 @@
+/*******************************************************************************
+ * Copyright (c) 2019 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.incubator.analysis.core.tests.weighted;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Collection;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.WeightedTree;
+import org.junit.Test;
+
+/**
+ * Test the {@link WeightedTree} class
+ *
+ * @author Geneviève Bastien
+ */
+@NonNullByDefault
+public class WeightedTreeTest {
+
+    private static final String OBJECT_NAME1 = "obj1";
+    private static final String OBJECT_NAME2 = "obj2";
+    private static final String OBJECT_NAME3 = "obj3";
+    private static final String OBJECT_NAME4 = "obj4";
+    private static final String OBJECT_NAME5 = "obj5";
+
+    /**
+     * Test the constructors
+     */
+    @Test
+    public void testConstructors() {
+        // Test the default constructor with only object
+        WeightedTree<String> wt = new WeightedTree<>(OBJECT_NAME1);
+        assertEquals("default constructor name", OBJECT_NAME1, wt.getObject());
+        assertEquals("default constructor initial weight", 0, wt.getWeight());
+        assertTrue("default constructor no children", wt.getChildren().isEmpty());
+        assertEquals("default depth", 1, wt.getMaxDepth());
+
+        // Test the constructor with initial weight
+        int initialWeight = 150;
+        wt = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+        assertEquals("constructor with weight name", OBJECT_NAME1, wt.getObject());
+        assertEquals("constructor with weight initial weight", initialWeight, wt.getWeight());
+        assertTrue("constructor with weight no children", wt.getChildren().isEmpty());
+        assertEquals("constructor with weight depth", 1, wt.getMaxDepth());
+    }
+
+    /**
+     * Test the {@link WeightedTree#merge(WeightedTree)} method
+     */
+    @Test
+    public void testSimpleMerge() {
+        int initialWeight = 150;
+        WeightedTree<String> wt = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+
+        // Merge without children
+        WeightedTree<String> wt2 = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+        wt.merge(wt2);
+        assertEquals("Value after merge", initialWeight * 2, wt.getWeight());
+        assertEquals("merged tree unmodified", initialWeight, wt2.getWeight());
+    }
+
+    /**
+     * Test merging trees for different objects, exception expected
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testMergeWrongObject() {
+        int initialWeight = 150;
+        WeightedTree<String> wt = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+        WeightedTree<String> wt2 = new WeightedTree<>(OBJECT_NAME2, initialWeight);
+        wt.merge(wt2);
+    }
+
+    /**
+     * Test the {@link WeightedTree#addToWeight(long)} method
+     */
+    @Test
+    public void testAddToWeight() {
+        int initialWeight = 150;
+        WeightedTree<String> wt = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+        assertEquals("initial weight", initialWeight, wt.getWeight());
+
+        wt.addToWeight(initialWeight);
+        assertEquals("initial weight", initialWeight * 2, wt.getWeight());
+    }
+
+    /**
+     * Test the {@link WeightedTree#copyOf()} method
+     */
+    @Test
+    public void testCopyOf() {
+        int initialWeight = 150;
+        int childWeight = 50;
+        WeightedTree<String> wt = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+
+        // Test the copy without children
+        WeightedTree<String> wtCopy = wt.copyOf();
+        assertEquals("same weight", wt.getWeight(), wtCopy.getWeight());
+
+        // Make sure modifying the copy does not affect the original
+        wtCopy.addToWeight(initialWeight);
+        assertEquals("New copy weight", initialWeight * 2, wtCopy.getWeight());
+        assertEquals("Unchanged original weight", initialWeight, wt.getWeight());
+
+        // Add a child to wt and copy, children are also copied
+        WeightedTree<String> child = new WeightedTree<>(OBJECT_NAME1, childWeight);
+        wt.addChild(child);
+        wtCopy = wt.copyOf();
+        assertEquals("Same weight", wt.getWeight(), wtCopy.getWeight());
+        Collection<WeightedTree<String>> children = wtCopy.getChildren();
+        assertEquals("No children copied", 1, children.size());
+
+        // Make sure modifying the child does not affect the original child
+        WeightedTree<String> childCopy = children.iterator().next();
+        childCopy.addToWeight(childWeight);
+        assertEquals("New child copy weight", childWeight * 2, childCopy.getWeight());
+        assertEquals("Unchanged original child weight", childWeight, child.getWeight());
+
+    }
+
+    /**
+     * Test the {@link WeightedTree#addChild(WeightedTree)} method
+     */
+    @Test
+    public void testAddChild() {
+        int initialWeight = 150;
+        WeightedTree<String> wt = new WeightedTree<>(OBJECT_NAME1, initialWeight);
+
+        int childWeight = 30;
+        WeightedTree<String> child1 = new WeightedTree<>(OBJECT_NAME2, childWeight);
+
+        // Add a first child
+        wt.addChild(child1);
+        assertEquals("Unchanged parent weight", initialWeight, wt.getWeight());
+        assertEquals("Unchanged child weight", childWeight, child1.getWeight());
+        assertEquals("Children of parent", 1, wt.getChildren().size());
+        assertTrue("No child to child", child1.getChildren().isEmpty());
+        WeightedTree<String> treeChild = wt.getChildren().iterator().next();
+        assertEquals("Child of parent", child1, treeChild);
+
+        // Add a second child for different object
+        WeightedTree<String> child2 = new WeightedTree<>(OBJECT_NAME3, childWeight);
+        wt.addChild(child2);
+        assertEquals("Unchanged parent weight", initialWeight, wt.getWeight());
+        assertEquals("Unchanged child weight", childWeight, child2.getWeight());
+        assertEquals("Children of parent", 2, wt.getChildren().size());
+
+        // Add a third child to merge with child1
+        WeightedTree<String> child3 = new WeightedTree<>(OBJECT_NAME2, childWeight);
+        wt.addChild(child3);
+        assertEquals("Unchanged parent weight", initialWeight, wt.getWeight());
+        assertEquals("Unchanged child weight", childWeight, child3.getWeight());
+        assertEquals("Children of parent", 2, wt.getChildren().size());
+        assertEquals("New tree child weight", childWeight * 2, treeChild.getWeight());
+
+        assertEquals("Max depth", 2, wt.getMaxDepth());
+
+        // Add wt as a child to a new parent tree
+        WeightedTree<String> parent = new WeightedTree<>(OBJECT_NAME4, initialWeight * 2);
+        parent.addChild(wt);
+        assertFalse("Parent's child", parent.getChildren().isEmpty());
+        treeChild = parent.getChildren().iterator().next();
+        assertEquals("no children lost", 2, treeChild.getChildren().size());
+        assertEquals("Final max depth", 3, parent.getMaxDepth());
+    }
+
+    /**
+     * Test the {@link WeightedTree#merge(WeightedTree)} method for objects that
+     * have many levels of similar children
+     */
+    @Test
+    public void testDeepMerge() {
+        /**
+         * Here's the layout of the objects to merge
+         *
+         * <pre>
+         *        1                 1
+         *       / \ \             / \ \
+         *      1   2 3           1   2 5
+         *     / \  |            / \  | |
+         *    4   5 2           3   4 1 2
+         *
+         * Expected:
+         *          1*2
+         *     /     |   \  \
+         *   1*2    2*2   3  5
+         *  / | \   / \      |
+         * 3 4*2 5 1   2     2
+         * </pre>
+         */
+        int level0Weight = 150;
+        int level1Weight = 40;
+        int level2Weight = 10;
+
+        // Prepare the objects to merge
+        // First object
+        WeightedTree<String> wtParent1 = new WeightedTree<>(OBJECT_NAME1, level0Weight);
+        WeightedTree<String> wtLevel1 = new WeightedTree<>(OBJECT_NAME1, level1Weight);
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME4, level2Weight));
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME5, level2Weight));
+        wtParent1.addChild(wtLevel1);
+
+        wtLevel1 = new WeightedTree<>(OBJECT_NAME2, level1Weight);
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME2, level2Weight));
+        wtParent1.addChild(wtLevel1);
+
+        wtParent1.addChild(new WeightedTree<>(OBJECT_NAME3, level1Weight));
+
+        // Second object
+        WeightedTree<String> wtParent2 = new WeightedTree<>(OBJECT_NAME1, level0Weight);
+        wtLevel1 = new WeightedTree<>(OBJECT_NAME1, level1Weight);
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME3, level2Weight));
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME4, level2Weight));
+        wtParent2.addChild(wtLevel1);
+
+        wtLevel1 = new WeightedTree<>(OBJECT_NAME2, level1Weight);
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME1, level2Weight));
+        wtParent2.addChild(wtLevel1);
+
+        wtLevel1 = new WeightedTree<>(OBJECT_NAME5, level1Weight);
+        wtLevel1.addChild(new WeightedTree<>(OBJECT_NAME2, level2Weight));
+        wtParent2.addChild(wtLevel1);
+
+        // Merge the objects and test its content
+        wtParent1.merge(wtParent2);
+
+        assertEquals("Level 0 Weight", level0Weight * 2, wtParent1.getWeight());
+        Collection<WeightedTree<String>> level1Children = wtParent1.getChildren();
+        assertEquals("Level 0 Nb children", 4, level1Children.size());
+        assertEquals("Max depth", 3, wtParent1.getMaxDepth());
+
+        for (WeightedTree<String> level1Child : level1Children) {
+            switch (level1Child.getObject()) {
+            case OBJECT_NAME1:
+            {
+                assertEquals("Level 1 Weight 1", level1Weight * 2, level1Child.getWeight());
+                Collection<WeightedTree<String>> level2Children = level1Child.getChildren();
+                assertEquals("Level 1 Nb children 1", 3, level2Children.size());
+                for (WeightedTree<String> level2Child : level2Children) {
+                    switch (level2Child.getObject()) {
+                    case OBJECT_NAME3: // Fall-through, same weight and children
+                    case OBJECT_NAME5:
+                        assertEquals("Level 2-1 Weight", level2Weight, level2Child.getWeight());
+                        assertTrue("Empty children at last level", level2Child.getChildren().isEmpty());
+                        break;
+                    case OBJECT_NAME4:
+                        assertEquals("Level 2-1 Weight", level2Weight * 2, level2Child.getWeight());
+                        assertTrue("Empty children at last level", level2Child.getChildren().isEmpty());
+                        break;
+                    default:
+                        fail("Unknown child " + level2Child.getObject());
+                        break;
+                    }
+                }
+            }
+                break;
+            case OBJECT_NAME2:
+            {
+                assertEquals("Level 1 Weight 2", level1Weight * 2, level1Child.getWeight());
+                Collection<WeightedTree<String>> level2Children = level1Child.getChildren();
+                assertEquals("Level 1 Nb children 2", 2, level2Children.size());
+                for (WeightedTree<String> level2Child : level2Children) {
+                    switch (level2Child.getObject()) {
+                    case OBJECT_NAME1: // Fall-through, same weight and children
+                    case OBJECT_NAME2:
+                        assertEquals("Level 2-2 Weight", level2Weight, level2Child.getWeight());
+                        assertTrue("Empty children at last level", level2Child.getChildren().isEmpty());
+                        break;
+                    default:
+                        fail("Unknown child " + level2Child.getObject());
+                        break;
+                    }
+                }
+            }
+                break;
+            case OBJECT_NAME3:
+                assertEquals("Level 1 Weight 3", level1Weight, level1Child.getWeight());
+                assertTrue("Empty children at last level", level1Child.getChildren().isEmpty());
+                break;
+            case OBJECT_NAME5:
+                assertEquals("Level 1 Weight 4", level1Weight, level1Child.getWeight());
+                Collection<WeightedTree<String>> level2Children = level1Child.getChildren();
+                assertEquals("Level 1 Nb children 4", 1, level2Children.size());
+                for (WeightedTree<String> level2Child : level2Children) {
+                    switch (level2Child.getObject()) {
+                    case OBJECT_NAME2:
+                        assertEquals("Level 2-2 Weight", level2Weight, level2Child.getWeight());
+                        assertTrue("Empty children at last level", level2Child.getChildren().isEmpty());
+                        break;
+                    default:
+                        fail("Unknown child " + level2Child.getObject());
+                        break;
+                    }
+                }
+                break;
+            default:
+                fail("Unknown child " + level1Child.getObject());
+                break;
+            }
+        }
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java
index 0113e40..7273fb2 100644
--- a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTree.java
@@ -44,25 +44,23 @@
     /**
      * Constructor
      *
-     * @param symbol
-     *            The symbol of the call site. It can eventually be resolved to
-     *            a string using the symbol providers
+     * @param object
+     *            The object that goes with this tree.
      */
-    public WeightedTree(T symbol) {
-        this(symbol, 0);
+    public WeightedTree(T object) {
+        this(object, 0);
     }
 
     /**
      * Constructor
      *
-     * @param symbol
-     *            The symbol of the call site. It can eventually be resolved to
-     *            a string using the symbol providers
+     * @param object
+     *            The object that goes with this tree.
      * @param initialWeight
      *            The initial length of this object
      */
-    public WeightedTree(T symbol, long initialWeight) {
-        fObject = symbol;
+    public WeightedTree(T object, long initialWeight) {
+        fObject = object;
         fParent = null;
         fWeight = initialWeight;
     }
@@ -71,7 +69,7 @@
      * Copy constructor
      *
      * @param copy
-     *            The call site to copy
+     *            The tree to copy
      */
     protected WeightedTree(WeightedTree<T> copy) {
         fObject = copy.fObject;
@@ -83,41 +81,41 @@
     }
 
     /**
-     * Get the aggregated value of this callsite. The units of this length will
-     * depend on the time of callstack. Typically, for sampled, it will be the
-     * number of times this symbol was hit, while for instrumented, it can be
-     * the total time spent in this callstack element
+     * Get the weight of this tree. The unit of this weight will depend on the
+     * metric it represents.
      *
-     * @return The aggregated value of this callsite
+     * @return The weight of this tree
      */
     public long getWeight() {
         return fWeight;
     }
 
     /**
-     * Make a copy of this callsite, with its statistics. Implementing classes
-     * should make sure they copy all fields of the callsite, including the
+     * Make a copy of this tree, with its statistics. Implementing classes
+     * should make sure they copy all fields of the tree, including the
      * statistics.
      *
-     * @return A copy of this aggregated call site
+     * This constructor recursively copies all the children.
+     *
+     * @return A copy of this weighted tree
      */
     public WeightedTree<T> copyOf() {
         return new WeightedTree<>(this);
     }
 
     /**
-     * Get the symbol associated with this callsite
+     * Get the object associated with this tree
      *
-     * @return The symbol for this callsite
+     * @return The object for this tree
      */
     public T getObject() {
         return fObject;
     }
 
     /**
-     * Get the caller of this callsite (parent)
+     * Get the parent of this tree
      *
-     * @return The caller of this callsite
+     * @return The parent of this tree
      */
     protected @Nullable WeightedTree<T> getParent() {
         return fParent;
@@ -134,16 +132,16 @@
     }
 
     /**
-     * Get the callees of this callsite, ie the functions called by this one
+     * Get the children of this tree
      *
-     * @return A collection of callees' callsites
+     * @return A collection of children trees
      */
     public Collection<WeightedTree<T>> getChildren() {
         return fChildren.values();
     }
 
     /**
-     * Add value to the length of this callsite
+     * Add value to the weight of this tree
      *
      * @param weight
      *            the amount to add to the length
@@ -153,35 +151,35 @@
     }
 
     /**
-     * Add a callee to this callsite. If a callee for the same object already
-     * exists, the data for both callees will be merged.
+     * Add a child to this tree. If a child for the same object already
+     * exists, the data for both children will be merged.
      *
-     * @param callee
-     *            the call site of the callee
+     * @param child
+     *            the child tree to add
      */
-    public void addChild(WeightedTree<T> callee) {
-        WeightedTree<T> callsite = fChildren.get(callee.getObject());
-        if (callsite == null) {
-            callee.setParent(this);
-            fChildren.put(callee.getObject(), callee);
+    public void addChild(WeightedTree<T> child) {
+        WeightedTree<T> childTree = fChildren.get(child.getObject());
+        if (childTree == null) {
+            child.setParent(this);
+            fChildren.put(child.getObject(), child);
             return;
         }
-        callsite.merge(callee);
+        childTree.merge(child);
     }
 
     /**
-     * Merge a callsite's data with this one. This method will modify the
-     * current callsite.
+     * Merge a tree's data with this one. This method will modify the current
+     * tree.
      *
      * It will first call {@link #mergeData(WeightedTree)} that needs to be
      * implemented for each implementation of this class.
      *
-     * It will then merge the callees of both callsites by adding the other's
-     * callees to this one.
+     * It will then merge the children of both trees by adding the other's
+     * children to this one.
      *
      * @param other
-     *            The call site to merge. It has to have the same symbol as the
-     *            current callsite otherwise it will throw an
+     *            The tree to merge. It has to have the same object as the
+     *            current tree otherwise it will throw an
      *            {@link IllegalArgumentException}
      */
     public final void merge(WeightedTree<T> other) {
@@ -194,29 +192,29 @@
     }
 
     /**
-     * Merge the data of two callsites. This should modify the current
-     * callsite's specific data. It is called by {@link #merge(WeightedTree)}
-     * and this method MUST NOT touch the callees of the callsites.
+     * Merge the data of two trees. This should modify the current
+     * tree's specific data. It is called by {@link #merge(WeightedTree)}
+     * and this method MUST NOT touch the children of the tree.
      *
      * @param other
-     *            The call site to merge to this one
+     *            The tree to merge to this one
      */
     protected void mergeData(WeightedTree<T> other) {
         // Nothing to do in main class
     }
 
     /**
-     * Merge the children callsites
+     * Merge the children trees
      *
      * @param other
-     *            The call site to merge to this one
+     *            The tree to merge to this one
      */
     private void mergeChildren(WeightedTree<T> other) {
         for (WeightedTree<T> otherChildSite : other.fChildren.values()) {
-            T childSymbol = otherChildSite.getObject();
-            WeightedTree<T> childSite = fChildren.get(childSymbol);
+            T childObject = otherChildSite.getObject();
+            WeightedTree<T> childSite = fChildren.get(childObject);
             if (childSite == null) {
-                fChildren.put(childSymbol, otherChildSite.copyOf());
+                fChildren.put(childObject, otherChildSite.copyOf());
             } else {
                 // combine children
                 childSite.merge(otherChildSite);
@@ -225,11 +223,11 @@
     }
 
     /**
-     * Get the maximum depth under and including this aggregated callsite. A
-     * depth of 1 means there is one element under and including this element.
+     * Get the maximum depth under and including this tree. A depth of 1 means
+     * there is one element under and including this element.
      *
-     * @return The maximum depth under and including this aggregated call site.
-     *         The minimal value for the depth is 1.
+     * @return The maximum depth under and including this tree. The minimal
+     *         value for the depth is 1.
      */
     public int getMaxDepth() {
         int maxDepth = 0;
@@ -243,17 +241,17 @@
      * Get other children of this tree that are not its direct descendants. It
      * can be used for instance to represent extra data, for example kernel
      * statuses for a callstack.
-     * <p>
-     * A weighted tree provider will advertise those potential children data
-     * that come with this tree, and consumers can then call this method with
-     * the index of this extra type, if the tree has more than one extra data
-     * set
+     *
+     * A {@link IWeightedTreeProvider} will advertise those potential children
+     * data that come with this tree, and consumers can then call this method
+     * with the index of this extra type, if the tree has more than one extra
+     * data set
      *
      * @param index
      *            The index of this extra children set, as provided by the
      *            {@link IWeightedTreeProvider#getExtraDataSets()} method.
      *
-     * @return The extra children sites
+     * @return The extra children trees
      */
     public Collection<WeightedTree<@NonNull T>> getExtraDataTrees(int index) {
         return Collections.emptyList();
@@ -261,7 +259,7 @@
 
     @Override
     public String toString() {
-        return "WeightedTreeNode: " + fObject; //$NON-NLS-1$
+        return "[" + fObject + "]: " + fWeight; //$NON-NLS-1$ //$NON-NLS-2$
     }
 
     @Override