blob: 65071b95c9627f69fb3182465e32737740fa0ffb [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 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.incubator.analysis.core.weighted.tree;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.timing.core.statistics.IStatistics;
/**
* A Weighted Tree class to describe hierarchical data with a weight. This class
* is a concrete class to describe a simple weighted tree, but it is also meant
* to be extended to support other metrics associated with each tree, apart from
* the weight.
*
* Note that the weight is such that the sum of the weight of the children is
* smaller or equal to the weight of the parent. Failure to comply to this will
* result in undefined behaviors when viewing the results.
*
* Also, if a child is added to the weighted tree for an object that is already
* present in the children of this tree, their data will be merged.
*
* @author Geneviève Bastien
* @param <T>
* The type of objects in this tree
*/
public class WeightedTree<@NonNull T> implements Comparable<WeightedTree<T>> {
private final T fObject;
private final Map<Object, WeightedTree<T>> fChildren = new HashMap<>();
private @Nullable WeightedTree<T> fParent;
private long fWeight = 0;
/**
* Constructor
*
* @param object
* The object that goes with this tree.
*/
public WeightedTree(T object) {
this(object, 0);
}
/**
* Constructor
*
* @param object
* The object that goes with this tree.
* @param initialWeight
* The initial length of this object
*/
public WeightedTree(T object, long initialWeight) {
fObject = object;
fParent = null;
fWeight = initialWeight;
}
/**
* Copy constructor
*
* @param copy
* The tree to copy
*/
protected WeightedTree(WeightedTree<T> copy) {
fObject = copy.fObject;
for (WeightedTree<T> entry : copy.fChildren.values()) {
fChildren.put(entry.getObject(), entry.copyOf());
}
fParent = copy.fParent;
fWeight = copy.fWeight;
}
/**
* Get the weight of this tree. The unit of this weight will depend on the
* metric it represents.
*
* @return The weight of this tree
*/
public long getWeight() {
return fWeight;
}
/**
* Make a copy of this tree, with its statistics. Implementing classes
* should make sure they copy all fields of the tree, including the
* statistics.
*
* This constructor recursively copies all the children.
*
* @return A copy of this weighted tree
*/
public WeightedTree<T> copyOf() {
return new WeightedTree<>(this);
}
/**
* Get the object associated with this tree
*
* @return The object for this tree
*/
public T getObject() {
return fObject;
}
/**
* Get the parent of this tree
*
* @return The parent of this tree
*/
protected @Nullable WeightedTree<T> getParent() {
return fParent;
}
/**
* Sets the parent of this tree
*
* @param parent
* The parent tree
*/
protected void setParent(WeightedTree<T> parent) {
fParent = parent;
}
/**
* Get the children of this tree
*
* @return A collection of children trees
*/
public Collection<WeightedTree<T>> getChildren() {
return fChildren.values();
}
/**
* Add value to the weight of this tree
*
* @param weight
* the amount to add to the length
*/
public void addToWeight(long weight) {
fWeight += weight;
}
/**
* Add a child to this tree. If a child for the same object already
* exists, the data for both children will be merged.
*
* @param child
* the child tree to add
*/
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;
}
childTree.merge(child);
}
/**
* 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 children of both trees by adding the other's
* children to this one.
*
* @param other
* 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) {
if (!other.getObject().equals(getObject())) {
throw new IllegalArgumentException("AggregatedStackTraces: trying to merge stack traces of different symbols"); //$NON-NLS-1$
}
fWeight += other.fWeight;
mergeData(other);
mergeChildren(other);
}
/**
* 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 tree to merge to this one
*/
protected void mergeData(WeightedTree<T> other) {
// Nothing to do in main class
}
/**
* Get the statistics for a metric at index. If the index {@literal <} 0,
* then the metric is the main weight.
*
* @param metricIndex
* The index in the list of the metric metric to get. If
* {@literal <} 0, then the metric is the weight.
* @return The statistics for the metric or <code>null</code> if not
* available
*/
public @Nullable IStatistics<?> getStatistics(int metricIndex) {
return null;
}
/**
* Merge the children trees
*
* @param other
* The tree to merge to this one
*/
private void mergeChildren(WeightedTree<T> other) {
for (WeightedTree<T> otherChildSite : other.fChildren.values()) {
T childObject = otherChildSite.getObject();
WeightedTree<T> childSite = fChildren.get(childObject);
if (childSite == null) {
fChildren.put(childObject, otherChildSite.copyOf());
} else {
// combine children
childSite.merge(otherChildSite);
}
}
}
/**
* 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 tree. The minimal
* value for the depth is 1.
*/
public int getMaxDepth() {
int maxDepth = 0;
for (WeightedTree<T> child : getChildren()) {
maxDepth = Math.max(maxDepth, child.getMaxDepth());
}
return maxDepth + 1;
}
/**
* 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.
*
* 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 trees
*/
public Collection<WeightedTree<@NonNull T>> getExtraDataTrees(int index) {
return Collections.emptyList();
}
@Override
public String toString() {
return "[" + fObject + "]: " + fWeight; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public int compareTo(WeightedTree<@NonNull T> o) {
return Long.compare(fWeight, o.fWeight);
}
}