blob: 0f19281133eaff60ed4180e76e4d52e1c449cab3 [file] [log] [blame]
/*******************************************************************************
* 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;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff.DifferentialWeightedTreeProvider;
import org.eclipse.tracecompass.incubator.analysis.core.weighted.tree.diff.DifferentialWeightedTreeSet;
import org.eclipse.tracecompass.tmf.core.util.Pair;
import com.google.common.collect.ImmutableList;
/**
* 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;
}
/**
* Does the differential between 2 weighted tree sets, ie for each
* comparable elements, what happened in tree set 2 differently than in tree
* set 1. The base weight come from the second tree set and the differential
* value will show the difference with the first tree.
* <p>
* Calling this method assumes the tree sets are comparable. It is the
* caller's responsibility to make sure the parameters make sense. If the 2
* tree sets come from different {@link IWeightedTreeProvider}, they should
* be of similar types, otherwise, the comparison may not make sense.
* </p>
* <p>
* The elements of each tree set are paired as follows:
* </p>
* <p>
* 1- If there is only one element in each tree, they will be paired.
* </p>
* <p>
* 2- If the same elements are present in both tree sets, they will be
* paired, all other elements are ignored
* </p>
* <p>
* 3- If no elements are paired, they will be paired by name (and
* hierarchical names, if it applies). Unmatched elements will be ignored.
* </p>
* <p>
* If elements are not paired at this point, it will return
* <code>null</code>
* </p>
*
* @param <N>
* The type of element in the tree
* @param provider
* The base provider of one of the trees, it will be used by the
* differential weighted tree provider to display the metrics and
* titles, etc.. It could be the provider of the second tree set,
* as it serves as the base values.
* @param first
* The first treeset to compare to
* @param second
* The second treeset to compare.
* @return A differential weighted tree provider wrapping the resulting tree
* set, or <code>null</code> if the 2 treesets have no elements in
* common
*/
public static <@NonNull N> @Nullable DifferentialWeightedTreeProvider<N> diffTreeSets(IWeightedTreeProvider<N, ?, WeightedTree<N>> provider,
IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> first,
IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> second) {
Collection<Pair<@NonNull ?, @NonNull ?>> pairedElements = pairElementsFromTrees(first, second);
if (pairedElements.isEmpty()) {
return null;
}
DifferentialWeightedTreeSet<N> treeSet = new DifferentialWeightedTreeSet<>();
for (Pair<@NonNull ?, @NonNull ?> pair : pairedElements) {
Collection<WeightedTree<N>> trees1 = first.getTreesFor(pair.getFirst());
Collection<WeightedTree<N>> trees2 = second.getTreesFor(pair.getSecond());
Collection<DifferentialWeightedTree<N>> diffTrees = WeightedTreeUtils.diffTrees(trees1, trees2);
for (DifferentialWeightedTree<N> tree: diffTrees) {
treeSet.addWeightedTree(pair.getFirst(), tree);
}
}
return new DifferentialWeightedTreeProvider<>(provider, treeSet);
}
private static <@NonNull N> Collection<Pair<@NonNull ?, @NonNull ?>> pairElementsFromTrees(IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> first, IWeightedTreeSet<N, @NonNull ?, WeightedTree<N>> second) {
Collection<@NonNull ?> elements1 = first.getElements();
Collection<@NonNull ?> elements2 = second.getElements();
// If there is only one element and it is not a tree, pair it
if (elements1.size() == 1 && elements2.size() == 1) {
@NonNull Object element1 = elements1.iterator().next();
@NonNull Object element2 = elements2.iterator().next();
if (!(element1 instanceof ITree) && !(element2 instanceof ITree)) {
return ImmutableList.of(new Pair(element1, element2));
}
}
// Try to find equal elements in both trees
Collection<Pair<@NonNull ?, @NonNull ?>> pairedElements = pairEqualElements(elements1, elements2);
if (!pairedElements.isEmpty()) {
return pairedElements;
}
// Compare ITree elements by names
pairedElements = pairSameNameElements(elements1, elements2);
return pairedElements;
}
private static Collection<Pair<@NonNull ?, @NonNull ?>> pairEqualElements(Collection<@NonNull ?> elements1, Collection<@NonNull ?> elements2) {
List<Pair<@NonNull ?, @NonNull ?>> pairedElements = new ArrayList<>();
for (@NonNull Object element1 : elements1) {
for (@NonNull Object element2 : elements2) {
if (element1.equals(element2)) {
pairedElements.add(new Pair<>(element1, element1));
if (element1 instanceof ITree && element2 instanceof ITree) {
pairedElements.addAll(pairEqualElements(((ITree) element1).getChildren(), ((ITree) element2).getChildren()));
}
break;
}
}
}
return pairedElements;
}
private static Collection<Pair<@NonNull ?, @NonNull ?>> pairSameNameElements(Collection<@NonNull ?> elements1, Collection<?> elements2) {
List<Pair<@NonNull ?, @NonNull ?>> pairedElements = new ArrayList<>();
for (@NonNull Object element1 : elements1) {
if (!(element1 instanceof ITree)) {
continue;
}
for (@NonNull Object element2 : elements2) {
if (!(element2 instanceof ITree)) {
continue;
}
if (((ITree) element1).getName().equals(((ITree) element2).getName())) {
pairedElements.add(new Pair<>(element1, element2));
pairedElements.addAll(pairSameNameElements(((ITree) element1).getChildren(), ((ITree) element2).getChildren()));
break;
}
}
}
return pairedElements;
}
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;
}
}