weighted tree: Add a differential tree class

Adds a utility method to calculate a differential tree between 2 trees.
Also add the class to represent a differential weighted tree and its
provider.

[added] Differential weighted tree classes

Change-Id: I458d7bdb402d4e957b7ee8c4868d3cb078d193ca
Signed-off-by: Geneviève Bastien <gbastien+lttng@versatic.net>
Reviewed-on: https://git.eclipse.org/r/149206
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Tested-by: CI Bot
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core.tests/src/org/eclipse/tracecompass/incubator/analysis/core/tests/weighted/WeightedTreeUtilsTest.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core.tests/src/org/eclipse/tracecompass/incubator/analysis/core/tests/weighted/WeightedTreeUtilsTest.java
new file mode 100644
index 0000000..1a4e7b6
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core.tests/src/org/eclipse/tracecompass/incubator/analysis/core/tests/weighted/WeightedTreeUtilsTest.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * 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.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.WeightedTree;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.WeightedTreeUtils;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff.DifferentialWeightedTree;
+import org.junit.Test;
+
+/**
+ * Test the operations on the the {@link WeightedTree} class
+ *
+ * @author Geneviève Bastien
+ */
+@NonNullByDefault
+public class WeightedTreeUtilsTest {
+
+    private static final Integer ELEMENT1 = 1;
+    private static final Integer ELEMENT2 = 2;
+    private static final Integer ELEMENT3 = 3;
+    private static final Integer ELEMENT4 = 4;
+    private static final Integer ELEMENT5 = 5;
+
+    /**
+     * Test the {@link WeightedTreeUtils#diffTrees(Collection, Collection)}
+     * method with simple trees
+     */
+    @Test
+    public void testDiffTree() {
+        // Create a simple tree with 2 elements and a level of children
+        List<WeightedTree<Integer>> tree1 = new ArrayList<>();
+        List<WeightedTree<Integer>> tree2 = new ArrayList<>();
+        /**
+         * <pre>
+         * Create a first collection of tree
+         * * 1  ->  10
+         *    | * 2 -> 4
+         *    | * 3 -> 3
+         *        | * 3 -> 1
+         * * 2  ->  10
+         *    | * 4 -> 5
+         *    | * 5 -> 5
+         * </pre>
+         */
+        WeightedTree<Integer> element1 = new WeightedTree<>(ELEMENT1, 10);
+        element1.addChild(new WeightedTree<>(ELEMENT2, 4));
+        WeightedTree<Integer> element2 = new WeightedTree<>(ELEMENT3, 3);
+        element1.addChild(element2);
+        element2.addChild(new WeightedTree<>(ELEMENT3, 1));
+        tree1.add(element1);
+        element1 = new WeightedTree<>(ELEMENT2, 10);
+        element1.addChild(new WeightedTree<>(ELEMENT4, 5));
+        element1.addChild(new WeightedTree<>(ELEMENT5, 5));
+        tree1.add(element1);
+
+        /**
+         * <pre>
+         * Create a second collection of tree
+         * * 1  ->  10
+         *    | * 2 -> 3
+         *    | * 3 -> 3
+         *        | * 3 -> 1
+         * * 2  ->  20
+         *    | * 4 -> 10
+         *    | * 5 -> 10
+         *        | * 3 -> 5
+         * </pre>
+         */
+        element1 = new WeightedTree<>(ELEMENT1, 10);
+        element1.addChild(new WeightedTree<>(ELEMENT2, 3));
+        element2 = new WeightedTree<>(ELEMENT3, 3);
+        element1.addChild(element2);
+        element2.addChild(new WeightedTree<>(ELEMENT3, 1));
+        tree2.add(element1);
+        element1 = new WeightedTree<>(ELEMENT2, 20);
+        element1.addChild(new WeightedTree<>(ELEMENT4, 10));
+        element2 = new WeightedTree<>(ELEMENT5, 10);
+        element1.addChild(element2);
+        element2.addChild(new WeightedTree<>(ELEMENT3, 5));
+        tree2.add(element1);
+
+        // Differentiate tree1 and tree2
+        Collection<DifferentialWeightedTree<Integer>> diffTrees = WeightedTreeUtils.diffTrees(tree1, tree2);
+        assertEquals("Size of differential tree", 2, diffTrees.size());
+        // Compare the first element
+        Collection<DifferentialWeightedTree<Integer>> nextTree = getAndVerifyTree(diffTrees, ELEMENT1, 10, 0);
+        assertEquals("Size of differential tree level 2", 2, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT2, 3, -0.25);
+        nextTree = getAndVerifyTree(nextTree, ELEMENT3, 3, 0);
+        assertEquals("Size of diferential tree level 3", 1, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT3, 1, 0);
+
+        // Compare the second element
+        nextTree = getAndVerifyTree(diffTrees, ELEMENT2, 20, 1.0);
+        assertEquals("Size of differential tree level 2", 2, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT4, 10, 1.0);
+        nextTree = getAndVerifyTree(nextTree, ELEMENT5, 10, 1.0);
+        assertEquals("Size of diferential tree level 3", 1, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT3, 5, Double.NaN);
+
+        // Reverse: Differentiate tree2 and tree1
+        diffTrees = WeightedTreeUtils.diffTrees(tree2, tree1);
+        assertEquals("Size of differential tree", 2, diffTrees.size());
+        // Compare the first element
+        nextTree = getAndVerifyTree(diffTrees, ELEMENT1, 10, 0);
+        assertEquals("Size of differential tree level 2", 2, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT2, 4, 0.33333);
+        nextTree = getAndVerifyTree(nextTree, ELEMENT3, 3, 0);
+        assertEquals("Size of diferential tree level 3", 1, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT3, 1, 0);
+
+        // Compare the first element
+        nextTree = getAndVerifyTree(diffTrees, ELEMENT2, 10, -0.5);
+        assertEquals("Size of differential tree level 2", 2, nextTree.size());
+        getAndVerifyTree(nextTree, ELEMENT4, 5, -0.5);
+        nextTree = getAndVerifyTree(nextTree, ELEMENT5, 5, -0.5);
+        assertTrue(nextTree.isEmpty());
+    }
+
+    private static Collection<DifferentialWeightedTree<Integer>> getAndVerifyTree(Collection<DifferentialWeightedTree<Integer>> diffTrees, Integer element, int expectedWeight, double expectedDiff) {
+        for (DifferentialWeightedTree<Integer> tree : diffTrees) {
+            List<DifferentialWeightedTree<Integer>> children = new ArrayList<>();
+            if (tree.getObject().equals(element)) {
+                // Found the tree, verify its values and return its children
+                assertEquals("Base weight of " + element, expectedWeight, tree.getWeight());
+                assertEquals("Differential value of " + element, expectedDiff, tree.getDifference(), 0.001);
+                for (WeightedTree<Integer> child : tree.getChildren()) {
+                    children.add((DifferentialWeightedTree<Integer>) child);
+                }
+                return children;
+            }
+        }
+        throw new NullPointerException("Tree not found for object " + element);
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF b/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF
index ca80ce5..b9b86cb 100644
--- a/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/META-INF/MANIFEST.MF
@@ -22,8 +22,10 @@
  org.eclipse.tracecompass.incubator.analysis.core.concepts,
  org.eclipse.tracecompass.incubator.analysis.core.model,
  org.eclipse.tracecompass.incubator.analysis.core.weighted.tree,
+ org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff,
  org.eclipse.tracecompass.incubator.internal.analysis.core;x-internal:=true,
  org.eclipse.tracecompass.incubator.internal.analysis.core.aspects;x-internal:=true,
  org.eclipse.tracecompass.incubator.internal.analysis.core.model;x-friends:="org.eclipse.tracecompass.incubator.analysis.core.tests,org.eclipse.tracecompass.incubator.callstack.core.tests"
-Import-Package: com.google.common.collect
+Import-Package: com.google.common.collect,
+ org.apache.commons.lang3
 Automatic-Module-Name: org.eclipse.tracecompass.incubator.analysis.core
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTreeUtils.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTreeUtils.java
new file mode 100644
index 0000000..fc95ac3
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/WeightedTreeUtils.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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.weighted.tree;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff.DifferentialWeightedTree;
+
+/**
+ * Utility methods to operate on {@link WeightedTree} objects
+ *
+ * @author Geneviève Bastien
+ */
+public final class WeightedTreeUtils {
+
+    private WeightedTreeUtils() {
+        // Nothing to do
+    }
+
+    /**
+     * Does the differential between 2 weighted trees, ie what happened in tree2
+     * differently than in tree1. The base weight come from the second tree and
+     * the differential value will show the difference with the first tree.
+     *
+     * @param <T>
+     *            The type of element in the tree
+     * @param first
+     *            The tree that will be differentiated.
+     * @param second
+     *            The tree to use as the base
+     * @return The differential weighted tree
+     */
+    public static <@NonNull T> Collection<DifferentialWeightedTree<T>> diffTrees(Collection<WeightedTree<T>> first, Collection<WeightedTree<T>> second) {
+        List<DifferentialWeightedTree<T>> diffTrees = new ArrayList<>();
+        for (WeightedTree<T> base : second) {
+            T object = base.getObject();
+            // Find the equivalent tree in the first collection
+            WeightedTree<T> other = findObject(first, object);
+            double diffWeight = other == null ? Double.NaN : (double) (base.getWeight() - other.getWeight()) / other.getWeight();
+            DifferentialWeightedTree<@NonNull T> diffTree = new DifferentialWeightedTree<>(base, object, base.getWeight(), diffWeight);
+            diffTrees.add(diffTree);
+
+            // Make the differential of the children
+            for (DifferentialWeightedTree<T> childTree : diffTrees(other == null ? Collections.emptyList() : other.getChildren(), base.getChildren())) {
+                diffTree.addChild(childTree);
+            }
+        }
+        return diffTrees;
+    }
+
+    private static @Nullable <@NonNull T> WeightedTree<T> findObject(Collection<WeightedTree<T>> tree, @NonNull T object) {
+        for (WeightedTree<T> other : tree) {
+            if (other.getObject().equals(object)) {
+                return other;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/DifferentialWeightedTree.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/DifferentialWeightedTree.java
new file mode 100644
index 0000000..9ed8c87
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/DifferentialWeightedTree.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * 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.weighted.tree.diff;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.WeightedTree;
+
+/**
+ * A class that represents a differential weighted tree. The weight is the base
+ * weight of one of the differentiated tree[set] and a differencial value is
+ * used to represent how it differs with the weight value of another tree.
+ *
+ * @author Geneviève Bastien
+ *
+ * @param <T>
+ *            The type of object this tree uses
+ */
+public class DifferentialWeightedTree<@NonNull T> extends WeightedTree<@NonNull T> {
+
+    private final double fDifference;
+    private final WeightedTree<@NonNull T> fOriginalTree;
+
+    /**
+     * Constructor
+     *
+     * @param originalTree
+     *            The base tree from which this differential tree was computed.
+     *            Used for additional metrics and texts.
+     * @param object
+     *            The object this tree is linked to
+     * @param initialWeight
+     *            The initial weight of this tree
+     * @param diffWeight
+     *            The differential weight with the base
+     */
+    public DifferentialWeightedTree(WeightedTree<T> originalTree, T object, long initialWeight, double diffWeight) {
+        super(object, initialWeight);
+        fDifference = diffWeight;
+        fOriginalTree = originalTree;
+    }
+
+    /**
+     * Get the differential value for this object. This value is the relative
+     * difference between the value of a first tree and the second tree
+     * compared. A positive value means the object's value is superior than the
+     * previous value. Conversely a negative difference means the second value
+     * is inferior to the initial value. 0 means there was no difference.
+     *
+     * It can also be {@link Double#NaN} if the value did not exist before
+     *
+     * @return The differential value
+     */
+    public double getDifference() {
+        return fDifference;
+    }
+
+    /**
+     * Get the base tree from which this differential weighted tree was computed
+     *
+     * @return The original base tree
+     */
+    public WeightedTree<@NonNull T> getOriginalTree() {
+        return fOriginalTree;
+    }
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/DifferentialWeightedTreeProvider.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/DifferentialWeightedTreeProvider.java
new file mode 100644
index 0000000..e540c1e
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/DifferentialWeightedTreeProvider.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * 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.weighted.tree.diff;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.IWeightedTreeProvider;
+import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.WeightedTree;
+
+/**
+ * Weighted tree provider that provides a differential weighted tree. Since any
+ * tree can be differentiated with other trees, this class is mostly a wrapper
+ * around the original tree provider, only the elements and trees are specific
+ * to this class.
+ *
+ * @author Geneviève Bastien
+ */
+public class DifferentialWeightedTreeProvider implements IWeightedTreeProvider<Object, String, DifferentialWeightedTree<Object>> {
+
+    private static final String DEFAULT_ELEMENT = "diff"; //$NON-NLS-1$
+    private static final Format FORMAT = new DecimalFormat("#.#"); //$NON-NLS-1$
+
+    private static final Format DIFFERENTIAL_FORMAT = new Format() {
+
+        /**
+         *
+         */
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public @Nullable StringBuffer format(@Nullable Object obj, @Nullable StringBuffer toAppendTo, @Nullable FieldPosition pos) {
+            StringBuffer buf = toAppendTo;
+            if (buf == null) {
+                buf = new StringBuffer();
+            }
+            if (obj instanceof Number) {
+                double num = ((Number) obj).doubleValue();
+                if (num == 0.0) {
+                    return buf.append("No Difference"); //$NON-NLS-1$
+                }
+                return buf.append((num > 0 ? "+" : "")).append(FORMAT.format(num * 100)).append("%"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+            }
+            return FORMAT.format(obj, toAppendTo, pos);
+        }
+
+        @Override
+        public @Nullable Object parseObject(@Nullable String source, @Nullable ParsePosition pos) {
+            return null;
+        }
+
+    };
+
+    private static final List<MetricType> WEIGHT_TYPES = Collections.singletonList(new MetricType("Differential", DataType.OTHER, DIFFERENTIAL_FORMAT)); //$NON-NLS-1$
+
+    private final Collection<DifferentialWeightedTree<Object>> fTrees;
+
+    private final IWeightedTreeProvider<Object, ?, WeightedTree<Object>> fOriginalTree;
+
+    /**
+     * Constructor
+     *
+     * @param originalTree
+     *            The original tree provider, used to get information for texts and metrics.
+     * @param trees
+     *            The differential tree
+     */
+    public DifferentialWeightedTreeProvider(IWeightedTreeProvider<Object, ?, WeightedTree<Object>> originalTree, Collection<DifferentialWeightedTree<Object>> trees) {
+        fTrees = trees;
+        fOriginalTree = originalTree;
+    }
+
+    @Override
+    public Collection<DifferentialWeightedTree<Object>> getTreesFor(String element) {
+        if (element.equals(DEFAULT_ELEMENT)) {
+            return fTrees;
+        }
+        return Collections.emptyList();
+    }
+
+    @Override
+    public Collection<String> getElements() {
+        return Collections.singleton(DEFAULT_ELEMENT);
+    }
+
+    @Override
+    public String getTitle() {
+        return "Differential tree"; //$NON-NLS-1$
+    }
+
+    @Override
+    public @NonNull MetricType getWeightType() {
+        return fOriginalTree.getWeightType();
+    }
+
+    @Override
+    public String toDisplayString(DifferentialWeightedTree<Object> tree) {
+        return fOriginalTree.toDisplayString(tree.getOriginalTree());
+    }
+
+    @Override
+    public List<MetricType> getAdditionalMetrics() {
+        return WEIGHT_TYPES;
+    }
+
+    @Override
+    public Object getAdditionalMetric(DifferentialWeightedTree<Object> object, int metricIndex) {
+        if (metricIndex == 0) {
+            return object.getDifference();
+        }
+        return StringUtils.EMPTY;
+    }
+
+
+}
diff --git a/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/package-info.java b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/package-info.java
new file mode 100644
index 0000000..9d63fc3
--- /dev/null
+++ b/callstack/org.eclipse.tracecompass.incubator.analysis.core/src/org/eclipse/tracecompass/incubator/analysis/core/weighted/tree/diff/package-info.java
@@ -0,0 +1,11 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff;