package rocks.inspectit.ui.rcp.editor.tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang.ArrayUtils;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.progress.PendingUpdateAdapter;
/**
* This tree viewer works in conjunction with the
* {@link org.eclipse.ui.progress.DeferredTreeContentManager} so that the expand function will work.
* <p>
* <b>IMPORTANT:</b> The class is licensed under the Eclipse Public License v1.0 as it includes the
* code from the {@link org.eclipse.jface.viewers.TreeViewer} class belonging to the Eclipse Rich
* Client Platform. EPL v1.0 license can be found
* <a href="https://www.eclipse.org/legal/epl-v10.html">here</a>.
* <p>
* Please relate to the LICENSEEXCEPTIONS.txt file for more information about license exceptions
* that apply regarding to InspectIT and Eclipse RCP and/or EPL Components.
*
* @author Patrice Bouillet
*
*/
public class DeferredTreeViewer extends TreeViewer {
/**
* Maps the parent widgets to the level so that we know how deep we want to go.
*/
private Map<Widget, Integer> parentWidgets = Collections.synchronizedMap(new HashMap<Widget, Integer>());
/**
* List of the elements that need to be expanded.
*/
private Set<Object> objectsToBeExpanded = Collections.synchronizedSet(new HashSet<>());
/**
* Object to be selected.
*/
private AtomicReference<Object> objectToSelect = new AtomicReference<>();
/**
* Creates a tree viewer on a newly-created tree control under the given parent. The tree
* control is created using the SWT style bits <code>MULTI, H_SCROLL, V_SCROLL,</code> and
* <code>BORDER</code>. The viewer has no input, no content provider, a default label provider,
* no sorter, and no filters.
*
* @param parent
* the parent control
*/
public DeferredTreeViewer(Composite parent) {
super(parent);
}
/**
* Creates a tree viewer on the given tree control. The viewer has no input, no content
* provider, a default label provider, no sorter, and no filters.
*
* @param tree
* the tree control
*/
public DeferredTreeViewer(Tree tree) {
super(tree);
}
/**
* Creates a tree viewer on a newly-created tree control under the given parent. The tree
* control is created using the given SWT style bits. The viewer has no input, no content
* provider, a default label provider, no sorter, and no filters.
*
* @param parent
* the parent control
* @param style
* the SWT style bits used to create the tree.
*/
public DeferredTreeViewer(Composite parent, int style) {
super(parent, style);
}
/**
* {@inheritDoc}
*/
@Override
protected void internalAdd(Widget widget, Object parentElement, Object[] childElements) {
// we have to activate our own filters first, stupid eclipse
// implementation which has got two different paths of applying filters
// ...
ViewerFilter[] filters = getFilters();
for (ViewerFilter filter : filters) {
childElements = filter.filter(this, parentElement, childElements);
}
super.internalAdd(widget, parentElement, childElements);
// check if we are currently in the process of expanding the child
// elements
if (parentWidgets.containsKey(widget)) {
// iterate over all child elements
for (Object object : childElements) {
// is it expandable
if (super.isExpandable(object)) {
// get the level
Integer level = parentWidgets.get(widget);
if (level == AbstractTreeViewer.ALL_LEVELS) {
super.expandToLevel(object, AbstractTreeViewer.ALL_LEVELS);
} else {
super.expandToLevel(object, level - 1);
}
}
}
}
if ((objectsToBeExpanded != null) && !objectsToBeExpanded.isEmpty()) {
// iterate over all child elements
for (Object object : childElements) {
// is object in List of objects that need to be expanded?
if (objectsToBeExpanded.contains(object)) {
// then expand it
if (!getExpandedState(object)) {
super.expandToLevel(object, 1);
}
}
}
}
// if there is object to be selected, we will selected if its parent is expanded
while (true) {
Object objToSelect = objectToSelect.get();
if ((objToSelect != null) && (!isRootElement(objToSelect) || getExpandedState(getParentElement(objToSelect)))) {
List<Object> selectionList = new ArrayList<>();
Widget w = internalGetWidgetToSelect(objToSelect);
if (w != null) {
if (objectToSelect.compareAndSet(objToSelect, null)) {
selectionList.add(w);
setSelection(selectionList);
break;
}
} else {
break;
}
} else {
break;
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void internalExpandToLevel(Widget widget, int level) {
if ((level > 1) || (AbstractTreeViewer.ALL_LEVELS == level)) {
// we want to open more than one level, have to take care of that.
Object data = widget.getData();
if (!(data instanceof PendingUpdateAdapter)) {
// just care about our own widgets
parentWidgets.put(widget, Integer.valueOf(level));
}
}
// when the widget is actually expanding, we have to remove its data from the list of object
// that
// needs to be expanded, if the data of the widget is found in the list
Object data = widget.getData();
if ((data != null) && objectsToBeExpanded.contains(data)) {
objectsToBeExpanded.remove(data);
}
super.internalExpandToLevel(widget, level);
}
/**
* {@inheritDoc}
*/
@Override
protected void internalRemove(Object[] elementsOrPaths) {
// we want to remove the parent of the PendingUpdateAdapter items from
// our Map
if (1 == elementsOrPaths.length) {
Object object = elementsOrPaths[0];
if (object instanceof PendingUpdateAdapter) {
Widget[] widgets = findItems(object);
if ((null != widgets) && (widgets.length > 0)) {
Widget widget = widgets[0];
Widget parentWidget = getParentItem((Item) widget);
if (parentWidgets.containsKey(parentWidget)) {
parentWidgets.remove(parentWidget);
}
}
}
}
super.internalRemove(elementsOrPaths);
}
/**
* Expands all ancestors of the given element or tree path so that the given element becomes
* visible in this viewer's tree control, and then expands the subtree rooted at the given
* element to the given level. The element will be then selected.
*
* @param elementOrTreePath
* the element
* @param level
* non-negative level, or <code>ALL_LEVELS</code> to expand all levels of the tree
*/
public void expandToObjectAndSelect(Object elementOrTreePath, int level) {
if (checkBusy()) {
return;
}
Object parent = getParentElement(elementOrTreePath);
// check if the element is already visible, or if it is root
if (((parent != null) && getExpandedState(parent)) || isRootElement(elementOrTreePath)) {
// then only set selection
Widget w = internalGetWidgetToSelect(elementOrTreePath);
if (null != w) {
// if widget is already available selected it
List<Object> selectionList = new ArrayList<>();
selectionList.add(w);
setSelection(selectionList);
// and overwrite any earlier set selection object
objectToSelect.set(null);
} else {
// otherwise set object to selected
objectToSelect.set(elementOrTreePath);
}
} else {
// get all the objects that need to be expanded so that object is visible
objectToSelect.set(elementOrTreePath);
List<Object> objectsToExpand = createObjectList(parent, new ArrayList<>());
if (!objectsToExpand.isEmpty()) {
objectsToBeExpanded.addAll(objectsToExpand);
Widget w = internalExpand(elementOrTreePath, true);
if (w != null) {
internalExpandToLevel(w, level);
}
} else {
// if the list if empty, this means that no object in the tree has to load the
// children, and they are all expanded, thus we can just select the wanted object
Widget w = internalGetWidgetToSelect(elementOrTreePath);
if (null != w) {
// if widget is here available
List<Object> selectionList = new ArrayList<>();
selectionList.add(w);
setSelection(selectionList);
// and overwrite any earlier set selection object
objectToSelect.set(null);
}
}
}
}
/**
* Expands all ancestors of the given element or tree path so that the given element becomes
* visible in this viewer's tree control and additionally expands the element if it has
* children.
*
* @param elementOrTreePath
* the element
* @param level
* non-negative level, or <code>ALL_LEVELS</code> to expand all levels of the tree
*/
public void expandObject(Object elementOrTreePath, int level) {
if (checkBusy()) {
return;
}
Object parent = getParentElement(elementOrTreePath);
// check if the element is already visible, or if it is root
if (!(((parent != null) && getExpandedState(parent)) || isRootElement(elementOrTreePath))) {
// get all the objects that need to be expanded so that object is visible
List<Object> objectsToExpand = createObjectList(parent, new ArrayList<>());
if (!objectsToExpand.isEmpty()) {
objectsToBeExpanded.addAll(objectsToExpand);
}
}
objectsToBeExpanded.add(elementOrTreePath);
Widget w = internalExpand(elementOrTreePath, true);
if (w != null) {
internalExpandToLevel(w, level);
}
}
/**
* Constructs the list of elements that need to be expanded, so that object supplied can be
* visible.
*
* @param object
* Object that expansion should reach.
* @param objectList
* List where the results are stored.
* @return List of objects for expansion.
*/
private List<Object> createObjectList(Object object, List<Object> objectList) {
if (areFiltersPassed(object) && !getExpandedState(object)) {
if (childrenLoaded(object)) {
// if children are loaded for this object we simply expand it directly
expandToLevel(object, 1);
} else {
if (objectList == null) {
objectList = new ArrayList<>();
}
objectList.add(object);
}
}
Object parent = getParentElement(object);
if (null != parent) {
createObjectList(parent, objectList);
}
return objectList;
}
/**
* Returns if all the filters are passed for the specific object.
*
* @param object
* Object to test.
* @return True if all the filters are passed, and thus object is visible in the tree. False
* otherwise.
*/
private boolean areFiltersPassed(Object object) {
ViewerFilter[] filters = getFilters();
if (null != filters) {
for (ViewerFilter filer : filters) {
if (!filer.select(this, getParentElement(object), object)) {
return false;
}
}
}
return true;
}
/**
* Tests if the children of the tree item have been loaded.
*
* @param object
* Object to test.
* @return True if the children have been fetched.
*/
private boolean childrenLoaded(Object object) {
Item[] children = getChildren(doFindItem(object));
if (null == children) {
return false;
}
for (Item item : children) {
if (!(item instanceof TreeItem)) {
return false;
}
}
return true;
}
/**
* Checks if the given element is one of the root object in the input list of the tree viewer.
*
* @param element
* Element to check.
* @return True if the element is one of the root objects.
*/
private boolean isRootElement(Object element) {
Object input = getRoot();
Object[] rootElemens = ((ITreeContentProvider) getContentProvider()).getElements(input);
return ArrayUtils.contains(rootElemens, element);
}
}