/*******************************************************************************
 * Copyright (c) 2004, 2017 IBM Corporation and others.
 *
 * 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
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Tom Schindl <tom.schindl@bestsolution.at> - concept of ViewerRow,
 *                                                 refactoring (bug 153993), bug 167323, 191468, 205419
 *     Matthew Hall - bug 221988
 *     Pawel Piech, WindRiver - bug 296573
 *     Lars Vogel <Lars.Vogel@gmail.com> - Bug 430873
 *******************************************************************************/

package org.eclipse.jface.viewers;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.internal.ExpandableNode;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

/**
 * A concrete viewer based on an SWT <code>Tree</code> control.
 * <p>
 * This class is not intended to be subclassed outside the viewer framework. It
 * is designed to be instantiated with a pre-existing SWT tree control and
 * configured with a domain-specific content provider, label provider, element
 * filter (optional), and element sorter (optional).
 * </p>
 * <p>
 * As of 3.2, TreeViewer supports multiple equal elements (each with a
 * different parent chain) in the tree. This support requires that clients
 * enable the element map by calling <code>setUseHashlookup(true)</code>.
 * </p>
 * <p>
 * Content providers for tree viewers must implement either the
 * {@link ITreeContentProvider} interface, (as of 3.2) the
 * {@link ILazyTreeContentProvider} interface, or (as of 3.3) the
 * {@link ILazyTreePathContentProvider}. If the content provider is an
 * <code>ILazyTreeContentProvider</code> or an
 * <code>ILazyTreePathContentProvider</code>, the underlying Tree must be
 * created using the {@link SWT#VIRTUAL} style bit, the tree viewer will not
 * support sorting or filtering, and hash lookup must be enabled by calling
 * {@link #setUseHashlookup(boolean)}.
 * </p>
 * <p>
 * Users setting up an editable tree with more than 1 column <b>have</b> to pass the
 * SWT.FULL_SELECTION style bit
 * </p>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class TreeViewer extends AbstractTreeViewer {

	private static final String VIRTUAL_DISPOSE_KEY = Policy.JFACE
			+ ".DISPOSE_LISTENER"; //$NON-NLS-1$

	/**
	 * This viewer's control.
	 */
	private Tree tree;

	/**
	 * Flag for whether the tree has been disposed of.
	 */
	private boolean treeIsDisposed = false;

	private boolean contentProviderIsLazy;

	private boolean contentProviderIsTreeBased;

	/**
	 * The row object reused
	 */
	private TreeViewerRow cachedRow;

	/**
	 * true if we are inside a preservingSelection() call
	 */
	private boolean insidePreservingSelection;

	/**
	 * 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 TreeViewer(Composite parent) {
		this(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
	}

	/**
	 * 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 TreeViewer(Composite parent, int style) {
		this(new Tree(parent, style));
	}

	/**
	 * 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 TreeViewer(Tree tree) {
		super();
		this.tree = tree;
		hookControl(tree);
	}

	@Override
	protected void addTreeListener(Control c, TreeListener listener) {
		((Tree) c).addTreeListener(listener);
	}

	@Override
	protected Widget getColumnViewerOwner(int columnIndex) {
		if (columnIndex < 0 || ( columnIndex > 0 && columnIndex >= getTree().getColumnCount() ) ) {
			return null;
		}

		if (getTree().getColumnCount() == 0)// Hang it off the table if it
			return getTree();

		return getTree().getColumn(columnIndex);
	}

	@Override
	protected Item[] getChildren(Widget o) {
		if (o instanceof TreeItem) {
			return ((TreeItem) o).getItems();
		}
		if (o instanceof Tree) {
			return ((Tree) o).getItems();
		}
		return null;
	}

	@Override
	public Control getControl() {
		return tree;
	}

	@Override
	protected boolean getExpanded(Item item) {
		return ((TreeItem) item).getExpanded();
	}

	@Override
	protected Item getItemAt(Point p) {
		TreeItem[] selection = tree.getSelection();

		if( selection.length == 1 ) {
			int columnCount = tree.getColumnCount();

			for( int i = 0; i < columnCount; i++ ) {
				if( selection[0].getBounds(i).contains(p) ) {
					return selection[0];
				}
			}
		}

		return getTree().getItem(p);
	}

	@Override
	protected int getItemCount(Control widget) {
		return ((Tree) widget).getItemCount();
	}

	@Override
	protected int getItemCount(Item item) {
		return ((TreeItem) item).getItemCount();
	}

	@Override
	protected Item[] getItems(Item item) {
		return ((TreeItem) item).getItems();
	}

	/**
	 * The tree viewer implementation of this <code>Viewer</code> framework
	 * method ensures that the given label provider is an instance of either
	 * <code>ITableLabelProvider</code> or <code>ILabelProvider</code>. If
	 * it is an <code>ITableLabelProvider</code>, then it provides a separate
	 * label text and image for each column. If it is an
	 * <code>ILabelProvider</code>, then it provides only the label text and
	 * image for the first column, and any remaining columns are blank.
	 */
	@Override
	public IBaseLabelProvider getLabelProvider() {
		return super.getLabelProvider();
	}

	@Override
	protected Item getParentItem(Item item) {
		return ((TreeItem) item).getParentItem();
	}

	@Override
	protected Item[] getSelection(Control widget) {
		return ((Tree) widget).getSelection();
	}

	/**
	 * Returns this tree viewer's tree control.
	 *
	 * @return the tree control
	 */
	public Tree getTree() {
		return tree;
	}

	@Override
	protected void hookControl(Control control) {
		super.hookControl(control);
		Tree treeControl = (Tree) control;

		if ((treeControl.getStyle() & SWT.VIRTUAL) != 0) {
			treeControl.addDisposeListener(e -> {
				treeIsDisposed = true;
				unmapAllElements();
			});
			treeControl.addListener(SWT.SetData, event -> {
				if (contentProviderIsLazy) {
					TreeItem item = (TreeItem) event.item;
					TreeItem parentItem = item.getParentItem();
					int index = event.index;
					virtualLazyUpdateWidget(
							parentItem == null ? (Widget) getTree()
									: parentItem, index);
				}
			});
		}
	}

	@Override
	protected ColumnViewerEditor createViewerEditor() {
		return new TreeViewerEditor(this,null,new ColumnViewerEditorActivationStrategy(this),ColumnViewerEditor.DEFAULT);
	}

	@Override
	protected Item newItem(Widget parent, int flags, int ix) {
		TreeItem item;

		if (parent instanceof TreeItem) {
			item = (TreeItem) createNewRowPart(getViewerRowFromItem(parent),
					flags, ix).getItem();
		} else {
			item = (TreeItem) createNewRowPart(null, flags, ix).getItem();
		}

		return item;
	}

	@Override
	protected void removeAll(Control widget) {
		((Tree) widget).removeAll();
	}

	@Override
	protected void setExpanded(Item node, boolean expand) {
		TreeItem treeItem = (TreeItem) node;
		if (!treeItem.isDisposed()) {
			treeItem.setExpanded(expand);
		}
		if (contentProviderIsLazy) {
			// force repaints to happen
			Control control = getControl();
			if (!control.isDisposed()) {
				control.update();
			}
		}
	}

	@Override
	protected void setSelection(List<Item> items) {

		Item[] current = getSelection(getTree());

		// Don't bother resetting the same selection
		if (isSameSelection(items, current)) {
			return;
		}

		TreeItem[] newItems = new TreeItem[items.size()];
		items.toArray(newItems);
		getTree().setSelection(newItems);
	}

	@Override
	protected void showItem(Item item) {
		getTree().showItem((TreeItem) item);
	}

	@Override
	protected Item getChild(Widget widget, int index) {
		if (widget instanceof TreeItem) {
			return ((TreeItem) widget).getItem(index);
		}
		if (widget instanceof Tree) {
			return ((Tree) widget).getItem(index);
		}
		return null;
	}

	@Override
	protected void assertContentProviderType(IContentProvider provider) {
		if (provider instanceof ILazyTreeContentProvider
				|| provider instanceof ILazyTreePathContentProvider) {
			return;
		}
		super.assertContentProviderType(provider);
	}

	@Override
	protected Object[] getRawChildren(Object parent) {
		if (contentProviderIsLazy) {
			return new Object[0];
		}
		return super.getRawChildren(parent);
	}

	@Override
	void preservingSelection(Runnable updateCode, boolean reveal) {
		if (insidePreservingSelection || !getPreserveSelection()){
			// avoid preserving the selection if called reentrantly,
			// see bug 172640
			updateCode.run();
			return;
		}
		// avoid costly calls to setSelectionToWidget() and getSelection() during
		// updateCode.run():
		insidePreservingSelection = true;
		try {
			super.preservingSelection(updateCode, reveal);
		} finally {
			insidePreservingSelection = false;
		}
	}

	/**
	 * For a TreeViewer with a tree with the VIRTUAL style bit set, set the
	 * number of children of the given element or tree path. To set the number
	 * of children of the invisible root of the tree, you can pass the input
	 * object or an empty tree path.
	 *
	 * @param elementOrTreePath
	 *            the element, or tree path
	 * @param count new number of children
	 *
	 * @since 3.2
	 */
	public void setChildCount(final Object elementOrTreePath, final int count) {
		if (checkBusy())
			return;
		preservingSelection(() -> {
			if (internalIsInputOrEmptyPath(elementOrTreePath)) {
				getTree().setItemCount(count);
				return;
			}
			Widget[] items = internalFindItems(elementOrTreePath);
			for (Widget item : items) {
				TreeItem treeItem = (TreeItem) item;
				treeItem.setItemCount(count);
			}
		});
	}

	/**
	 * For a TreeViewer with a tree with the VIRTUAL style bit set, replace the
	 * given parent's child at index with the given element. If the given parent
	 * is this viewer's input or an empty tree path, this will replace the root
	 * element at the given index.
	 * <p>
	 * This method should be called by implementers of ILazyTreeContentProvider
	 * to populate this viewer.
	 * </p>
	 *
	 * @param parentElementOrTreePath
	 *            the parent of the element that should be updated, or the tree
	 *            path to that parent
	 * @param index
	 *            the index in the parent's children
	 * @param element
	 *            the new element
	 *
	 * @see #setChildCount(Object, int)
	 * @see ILazyTreeContentProvider
	 * @see ILazyTreePathContentProvider
	 *
	 * @since 3.2
	 */
	public void replace(final Object parentElementOrTreePath, final int index,
			final Object element) {
		if (checkBusy())
			return;
		Item[] selectedItems = insidePreservingSelection ? null : getSelection(getControl());
		TreeSelection selection = insidePreservingSelection ? null : (TreeSelection) getSelection();
		Widget[] itemsToDisassociate;
		if (parentElementOrTreePath instanceof TreePath) {
			TreePath elementPath = ((TreePath) parentElementOrTreePath)
					.createChildPath(element);
			itemsToDisassociate = internalFindItems(elementPath);
		} else {
			itemsToDisassociate = internalFindItems(element);
		}
		if (internalIsInputOrEmptyPath(parentElementOrTreePath)) {
			if (index < tree.getItemCount()) {
				TreeItem item = tree.getItem(index);
				if (!insidePreservingSelection) {
					selection = adjustSelectionForReplace(selectedItems, selection, item, element, getRoot());
				}
				// disassociate any different item that represents the
				// same element under the same parent (the tree)
				for (Widget widget : itemsToDisassociate) {
					if (widget instanceof TreeItem) {
						TreeItem itemToDisassociate = (TreeItem) widget;
						if (itemToDisassociate != item
								&& itemToDisassociate.getParentItem() == null) {
							int indexToDisassociate = getTree().indexOf(
									itemToDisassociate);
							disassociate(itemToDisassociate);
							getTree().clear(indexToDisassociate, true);
						}
					}
				}
				Object oldData = item.getData();
				updateItem(item, element);
				if (!TreeViewer.this.equals(oldData, element)) {
					item.clearAll(true);
				}
			}
		} else {
			Widget[] parentItems = internalFindItems(parentElementOrTreePath);
			for (Widget widget : parentItems) {
				TreeItem parentItem = (TreeItem) widget;
				if (index < parentItem.getItemCount()) {
					TreeItem item = parentItem.getItem(index);
					if (!insidePreservingSelection) {
						selection = adjustSelectionForReplace(selectedItems, selection, item, element,
								parentItem.getData());
					}
					// disassociate any different item that represents the
					// same element under the same parent (the tree)
					for (Widget widgetToDisassociate : itemsToDisassociate) {
						if (widgetToDisassociate instanceof TreeItem) {
							TreeItem itemToDisassociate = (TreeItem) widgetToDisassociate;
							if (itemToDisassociate != item
									&& itemToDisassociate.getParentItem() == parentItem) {
								int indexToDisaccociate = parentItem
										.indexOf(itemToDisassociate);
								disassociate(itemToDisassociate);
								parentItem.clear(indexToDisaccociate, true);
							}
						}
					}
					Object oldData = item.getData();
					updateItem(item, element);
					if (!TreeViewer.this.equals(oldData, element)) {
						item.clearAll(true);
					}
				}
			}
		}
		// Restore the selection if we are not already in a nested preservingSelection:
		if (!insidePreservingSelection) {
			setSelectionToWidget(selection, false);
			// send out notification if old and new differ
			ISelection newSelection = getSelection();
			if (!newSelection.equals(selection)) {
				handleInvalidSelection(selection, newSelection);
			}
		}
	}

	/**
	 * Fix for bug 185673: If the currently replaced item was selected, add it
	 * to the selection that is being restored. Only do this if its getData() is
	 * currently null
	 *
	 * @param selectedItems
	 * @param selection
	 * @param item
	 * @param element
	 * @return the adjusted selection
	 */
	private TreeSelection adjustSelectionForReplace(Item[] selectedItems,
			TreeSelection selection, TreeItem item, Object element, Object parentElement) {
		if (item.getData() != null) {
			// Don't do anything - we are not seeing an instance of bug 185673
			return selection;
		}
		for (Item selectedItem : selectedItems) {
			if (item == selectedItem) {
				// The current item was selected, but its data is null.
				// The data will be replaced by the given element, so to keep
				// it selected, we have to add it to the selection.
				TreePath[] originalPaths = selection.getPaths();
				int length = originalPaths.length;
				TreePath[] paths = new TreePath[length + 1];
				System.arraycopy(originalPaths, 0, paths, 0, length);
				// set the element temporarily so that we can call getTreePathFromItem
				item.setData(element);
				paths[length] = getTreePathFromItem(item);
				item.setData(null);
				return new TreeSelection(paths, selection.getElementComparer());
			}
		}
		// The item was not selected, return the given selection
		return selection;
	}

	@Override
	public boolean isExpandable(Object element) {
		if (contentProviderIsLazy) {
			TreeItem treeItem = (TreeItem) internalExpand(element, false);
			if (treeItem == null) {
				return false;
			}
			virtualMaterializeItem(treeItem);
			return treeItem.getItemCount() > 0;
		}
		if (element instanceof ExpandableNode) {
			return false;
		}
		return super.isExpandable(element);
	}

	@Override
	protected Object getParentElement(Object element) {
		boolean oldBusy = isBusy();
		setBusy(true);
		try {
			if (contentProviderIsLazy && !contentProviderIsTreeBased && !(element instanceof TreePath)) {
				ILazyTreeContentProvider lazyTreeContentProvider = (ILazyTreeContentProvider) getContentProvider();
				return lazyTreeContentProvider.getParent(element);
			}
			if (contentProviderIsLazy && contentProviderIsTreeBased && !(element instanceof TreePath)) {
				ILazyTreePathContentProvider lazyTreePathContentProvider = (ILazyTreePathContentProvider) getContentProvider();
				TreePath[] parents = lazyTreePathContentProvider
				.getParents(element);
				if (parents != null && parents.length > 0) {
					return parents[0];
				}
			}
			return super.getParentElement(element);
		} finally {
			setBusy(oldBusy);
		}
	}

	@Override
	void createChildren(Widget widget, boolean materialize) {
		if (contentProviderIsLazy) {
			Object element = widget.getData();
			if (element == null && widget instanceof TreeItem) {
				// parent has not been materialized
				virtualMaterializeItem((TreeItem) widget);
				// try getting the element now that updateElement was called
				element = widget.getData();
			}
			if (element ==  null) {
				// give up because the parent is still not materialized
				return;
			}
			Item[] children = getChildren(widget);
			if (children.length == 1 && children[0].getData() == null) {
				// found a dummy node
				virtualLazyUpdateChildCount(widget, children.length);
				children = getChildren(widget);
			}
			// touch all children to make sure they are materialized
			for (int i = 0; i < children.length; i++) {
				if (children[i].getData() == null) {
					if (materialize) {
						virtualLazyUpdateWidget(widget, i);
					} else {
						((TreeItem)children[i]).clearAll(true);
					}
				}
			}
			return;
		}
		super.createChildren(widget, materialize);
	}

	@Override
	protected void internalAdd(Widget widget, Object parentElement,
			Object[] childElements) {
		if (contentProviderIsLazy) {
			if (widget instanceof TreeItem) {
				TreeItem ti = (TreeItem) widget;
				int count = ti.getItemCount() + childElements.length;
				ti.setItemCount(count);
				ti.clearAll(false);
			} else {
				Tree t = (Tree) widget;
				t.setItemCount(t.getItemCount() + childElements.length);
				t.clearAll(false);
			}
			return;
		}
		super.internalAdd(widget, parentElement, childElements);
	}

	private void virtualMaterializeItem(TreeItem treeItem) {
		if (treeItem.getData() != null) {
			// already materialized
			return;
		}
		if (!contentProviderIsLazy) {
			return;
		}
		int index;
		Widget parent = treeItem.getParentItem();
		if (parent == null) {
			parent = treeItem.getParent();
		}
		Object parentElement = parent.getData();
		if (parentElement != null) {
			if (parent instanceof Tree) {
				index = ((Tree) parent).indexOf(treeItem);
			} else {
				index = ((TreeItem) parent).indexOf(treeItem);
			}
			virtualLazyUpdateWidget(parent, index);
		}
	}

	@Override
	protected void internalRefreshStruct(Widget widget, Object element,
			boolean updateLabels) {
		if (contentProviderIsLazy) {
			// clear all starting with the given widget
			if (widget instanceof Tree) {
				((Tree) widget).clearAll(true);
			} else if (widget instanceof TreeItem) {
				((TreeItem) widget).clearAll(true);
			}
			int index = 0;
			Widget parent = null;
			if (widget instanceof TreeItem) {
				TreeItem treeItem = (TreeItem) widget;
				parent = treeItem.getParentItem();
				if (parent == null) {
					parent = treeItem.getParent();
				}
				if (parent instanceof Tree) {
					index = ((Tree) parent).indexOf(treeItem);
				} else {
					index = ((TreeItem) parent).indexOf(treeItem);
				}
			}
			virtualRefreshExpandedItems(parent, widget, element, index);
			return;
		}
		super.internalRefreshStruct(widget, element, updateLabels);
	}

	/**
	 * Traverses the visible (expanded) part of the tree and updates child
	 * counts.
	 *
	 * @param parent the parent of the widget, or <code>null</code> if the widget is the tree
	 * @param widget
	 * @param element
	 * @param index the index of the widget in the children array of its parent, or 0 if the widget is the tree
	 */
	private void virtualRefreshExpandedItems(Widget parent, Widget widget, Object element, int index) {
		if (widget instanceof Tree) {
			if (element == null) {
				((Tree) widget).setItemCount(0);
				return;
			}
			virtualLazyUpdateChildCount(widget, getChildren(widget).length);
		} else if (((TreeItem) widget).getExpanded()) {
			// prevent SetData callback
			((TreeItem)widget).setText(" "); //$NON-NLS-1$
			virtualLazyUpdateWidget(parent, index);
		} else {
			return;
		}
		Item[] items = getChildren(widget);
		for (int i = 0; i < items.length; i++) {
			Item item = items[i];
			Object data = item.getData();
			virtualRefreshExpandedItems(widget, item, data, i);
		}
	}

	/*
	 * To unmap elements correctly, we need to register a dispose listener with
	 * the item if the tree is virtual.
	 */
	@Override
	protected void mapElement(Object element, final Widget item) {
		super.mapElement(element, item);
		// make sure to unmap elements if the tree is virtual
		if ((getTree().getStyle() & SWT.VIRTUAL) != 0) {
			// only add a dispose listener if item hasn't already on assigned
			// because it is reused
			if (item.getData(VIRTUAL_DISPOSE_KEY) == null) {
				item.setData(VIRTUAL_DISPOSE_KEY, Boolean.TRUE);
				item.addDisposeListener(e -> {
					if (!treeIsDisposed) {
						Object data = item.getData();
						if (usingElementMap() && data != null) {
							unmapElement(data, item);
						}
					}
				});
			}
		}
	}

	@Override
	protected ViewerRow getViewerRowFromItem(Widget item) {
		if( cachedRow == null ) {
			cachedRow = new TreeViewerRow((TreeItem) item);
		} else {
			cachedRow.setItem((TreeItem) item);
		}

		return cachedRow;
	}

	/**
	 * Create a new ViewerRow at rowIndex
	 *
	 * @param parent
	 * @param style
	 * @param rowIndex
	 * @return ViewerRow
	 */
	private ViewerRow createNewRowPart(ViewerRow parent, int style, int rowIndex) {
		if (parent == null) {
			if (rowIndex >= 0) {
				return getViewerRowFromItem(new TreeItem(tree, style, rowIndex));
			}
			return getViewerRowFromItem(new TreeItem(tree, style));
		}

		if (rowIndex >= 0) {
			return getViewerRowFromItem(new TreeItem((TreeItem) parent.getItem(),
					SWT.NONE, rowIndex));
		}

		return getViewerRowFromItem(new TreeItem((TreeItem) parent.getItem(),
				SWT.NONE));
	}

	@Override
	protected void internalInitializeTree(Control widget) {
		if (contentProviderIsLazy) {
			if (widget instanceof Tree && widget.getData() != null) {
				virtualLazyUpdateChildCount(widget, 0);
				return;
			}
		}
		super.internalInitializeTree(tree);
	}

	@Override
	protected void updatePlus(Item item, Object element) {
		if (contentProviderIsLazy) {
			Object data = item.getData();
			int itemCount = 0;
			if (data != null) {
				// item is already materialized
				itemCount = ((TreeItem) item).getItemCount();
			}
			virtualLazyUpdateHasChildren(item, itemCount);
		} else {
			super.updatePlus(item, element);
		}
	}

	/**
	 * Removes the element at the specified index of the parent.  The selection is updated if required.
	 *
	 * @param parentOrTreePath the parent element, the input element, or a tree path to the parent element
	 * @param index child index
	 * @since 3.3
	 */
	public void remove(final Object parentOrTreePath, final int index) {
		if (checkBusy())
			return;
		// in case preservingSelection() is nested avoid getSelection():
		final List<TreePath> oldSelection = insidePreservingSelection ? null : new LinkedList<>(
				Arrays.asList(((TreeSelection) getSelection()).getPaths()));
		preservingSelection(() -> {
			TreePath removedPath = null;
			if (internalIsInputOrEmptyPath(parentOrTreePath)) {
				Tree tree = (Tree) getControl();
				if (index < tree.getItemCount()) {

					if (getItemsLimit() > 0 && hasLimitedChildrenItems(tree)) {
						internalRefreshStruct(tree, getInput(), false);
						return;
					}

					TreeItem item1 = tree.getItem(index);
					if (item1.getData() != null) {
						removedPath = getTreePathFromItem(item1);
						disassociate(item1);
					}
					item1.dispose();
				}
			} else {
				Widget[] parentItems = internalFindItems(parentOrTreePath);
				for (Widget parentWidget : parentItems) {
					TreeItem parentItem = (TreeItem) parentWidget;
					if (parentItem.isDisposed())
						continue;

					if (getItemsLimit() > 0 && hasLimitedChildrenItems(parentWidget)) {
						internalRefreshStruct(parentWidget, parentWidget.getData(), false);
						continue;
					}

					if (index < parentItem.getItemCount()) {
						TreeItem item2 = parentItem.getItem(index);

						if (item2.getData() == null) {
							// If getData()==null and index == 0, and the
							// parent item is collapsed, then we are
							// being asked to remove the dummy node. We'll
							// just ignore the request to remove the dummy
							// node (bug 292322 and bug 296573).
							if (index > 0 || parentItem.getExpanded()) {
								item2.dispose();
							}
						} else {
							removedPath = getTreePathFromItem(item2);
							disassociate(item2);
							item2.dispose();
						}
					}
				}
			}
			if (removedPath != null && oldSelection != null) {
				boolean removed = false;
				for (Iterator<TreePath> it = oldSelection.iterator(); it.hasNext();) {
					TreePath path = it.next();
					if (path.startsWith(removedPath, getComparer())) {
						it.remove();
						removed = true;
					}
				}
				if (removed) {
					setSelection(new TreeSelection(
							oldSelection
									.toArray(new TreePath[oldSelection
											.size()]), getComparer()),
							false);
				}

			}
		});
	}

	@Override
	protected void handleTreeExpand(TreeEvent event) {
		// Fix for Bug 271744 because windows expanding doesn't fire a focus lost
		if( isCellEditorActive() ) {
			applyEditorValue();
		}

		if (contentProviderIsLazy) {
			if (event.item.getData() != null) {
				Item[] children = getChildren(event.item);
				if (children.length == 1 && children[0].getData()==null) {
					// we have a dummy child node, ask for an updated child
					// count
					virtualLazyUpdateChildCount(event.item, children.length);
				}
				fireTreeExpanded(new TreeExpansionEvent(this, event.item
						.getData()));
			}
			return;
		}
		super.handleTreeExpand(event);
	}

	@Override
	protected void handleTreeCollapse(TreeEvent event) {
		// Fix for Bug 271744 because windows is firing collapse before
		// focus lost event
		if( isCellEditorActive() ) {
			applyEditorValue();
		}

		super.handleTreeCollapse(event);
	}

	/**
	 * Sets the content provider used by this <code>TreeViewer</code>.
	 * <p>
	 * Content providers for tree viewers must implement either
	 * {@link ITreeContentProvider}, or {@link ITreePathContentProvider}, or
	 * {@link ILazyTreeContentProvider}, or
	 * {@link ILazyTreePathContentProvider}.
	 */
	@Override
	public void setContentProvider(IContentProvider provider) {
		contentProviderIsLazy = (provider instanceof ILazyTreeContentProvider)
				|| (provider instanceof ILazyTreePathContentProvider);
		contentProviderIsTreeBased = provider instanceof ILazyTreePathContentProvider;
		super.setContentProvider(provider);
	}

	/**
	 * For a TreeViewer with a tree with the VIRTUAL style bit set, inform the
	 * viewer about whether the given element or tree path has children. Avoid
	 * calling this method if the number of children has already been set.
	 *
	 * @param elementOrTreePath
	 *            the element, or tree path
	 * @param hasChildren the new state for the element or tree path
	 *
	 * @since 3.3
	 */
	public void setHasChildren(final Object elementOrTreePath, final boolean hasChildren) {
		if (checkBusy())
			return;
		preservingSelection(() -> {
			if (internalIsInputOrEmptyPath(elementOrTreePath)) {
				if (hasChildren) {
					virtualLazyUpdateChildCount(getTree(),
							getChildren(getTree()).length);
				} else {
					setChildCount(elementOrTreePath, 0);
				}
				return;
			}
			Widget[] items = internalFindItems(elementOrTreePath);
			for (Widget widget : items) {
				TreeItem item = (TreeItem) widget;
				if (!hasChildren) {
					item.setItemCount(0);
				} else if (!item.getExpanded()) {
					item.setItemCount(1);
					TreeItem child = item.getItem(0);
					if (child.getData() != null) {
						disassociate(child);
					}
					item.clear(0, true);
				} else {
					virtualLazyUpdateChildCount(item, item.getItemCount());
				}
			}
		});
	}

	/**
	 * Update the widget at index.
	 * @param widget
	 * @param index
	 */
	private void virtualLazyUpdateWidget(Widget widget, int index) {
		boolean oldBusy = isBusy();
		setBusy(false);
		try {
			if (contentProviderIsTreeBased) {
				TreePath treePath;
				if (widget instanceof Item) {
					if (widget.getData() == null) {
						// we need to materialize the parent first
						// see bug 167668
						// however, that would be too risky
						// see bug 182782 and bug 182598
						// so we just ignore this call altogether
						// and don't do this: virtualMaterializeItem((TreeItem) widget);
						return;
					}
					treePath = getTreePathFromItem((Item) widget);
				} else {
					treePath = TreePath.EMPTY;
				}
				((ILazyTreePathContentProvider) getContentProvider())
						.updateElement(treePath, index);
			} else {
				((ILazyTreeContentProvider) getContentProvider()).updateElement(
						widget.getData(), index);
			}
		} finally {
			setBusy(oldBusy);
		}
	}

	/**
	 * Update the child count
	 * @param widget
	 * @param currentChildCount
	 */
	private void virtualLazyUpdateChildCount(Widget widget, int currentChildCount) {
		boolean oldBusy = isBusy();
		setBusy(false);
		try {
			if (contentProviderIsTreeBased) {
				TreePath treePath;
				if (widget instanceof Item) {
					treePath = getTreePathFromItem((Item) widget);
				} else {
					treePath = TreePath.EMPTY;
				}
				((ILazyTreePathContentProvider) getContentProvider())
				.updateChildCount(treePath, currentChildCount);
			} else {
				((ILazyTreeContentProvider) getContentProvider()).updateChildCount(widget.getData(), currentChildCount);
			}
		} finally {
			setBusy(oldBusy);
		}
	}

	/**
	 * Update the item with the current child count.
	 * @param item
	 * @param currentChildCount
	 */
	private void virtualLazyUpdateHasChildren(Item item, int currentChildCount) {
		boolean oldBusy = isBusy();
		setBusy(false);
		try {
			if (contentProviderIsTreeBased) {
				TreePath treePath;
				treePath = getTreePathFromItem(item);
				if (currentChildCount == 0 || !((TreeItem)item).getExpanded()) {
					// item is not expanded (but may have a plus currently)
					((ILazyTreePathContentProvider) getContentProvider())
					.updateHasChildren(treePath);
				} else {
					((ILazyTreePathContentProvider) getContentProvider())
					.updateChildCount(treePath, currentChildCount);
				}
			} else {
				((ILazyTreeContentProvider) getContentProvider()).updateChildCount(item.getData(), currentChildCount);
			}
		} finally {
			setBusy(oldBusy);
		}
	}

	@Override
	protected void disassociate(Item item) {
		if (contentProviderIsLazy) {
			// avoid causing a callback:
			item.setText(" "); //$NON-NLS-1$
		}
		super.disassociate(item);
	}

	@Override
	protected int doGetColumnCount() {
		return tree.getColumnCount();
	}

	/**
	 * Sets a new selection for this viewer and optionally makes it visible.
	 * <p>
	 * <b>Currently the <code>reveal</code> parameter is not honored because
	 * {@link Tree} does not provide an API to only select an item without
	 * scrolling it into view</b>
	 * </p>
	 *
	 * @param selection
	 *            the new selection
	 * @param reveal
	 *            <code>true</code> if the selection is to be made visible,
	 *            and <code>false</code> otherwise
	 */
	@Override
	public void setSelection(ISelection selection, boolean reveal) {
		super.setSelection(selection, reveal);
	}

	@Override
	public void editElement(Object element, int column) {
		if( element instanceof TreePath ) {
			try {
				getControl().setRedraw(false);
				setSelection(new TreeSelection((TreePath) element));
				TreeItem[] items = tree.getSelection();

				if( items.length == 1 ) {
					ViewerRow row = getViewerRowFromItem(items[0]);

					if (row != null) {
						ViewerCell cell = row.getCell(column);
						if (cell != null) {
							triggerEditorActivationEvent(new ColumnViewerEditorActivationEvent(cell));
						}
					}
				}
			} finally {
				getControl().setRedraw(true);
			}
		} else {
			super.editElement(element, column);
		}
	}

	@Override
	void handleExpandableNodeClicked(Widget w) {
		if (!(w instanceof Item item)) {
			return;
		}

		Object data = item.getData();
		if (data == null) {
			return;
		}

		Object[] children = getSortedChildren(data);
		if (children.length == 0) {
			return;
		}

		boolean oldBusy = isBusy();
		Tree tree = getTree();
		try {
			setBusy(true);
			tree.setRedraw(false);

			Widget parent = getParentItem(item);
			if (parent == null) {
				parent = getControl();
			}

			// destroy widget
			disassociate(item);
			item.dispose();

			// create children on parent
			for (Object element : children) {
				createTreeItem(parent, element, -1);
			}

			// If we've expanded but still have not reached the limit
			// select new expandable node, so user can click through
			// to the end
			Object lastElement = getLastElement(parent);
			if (lastElement instanceof ExpandableNode node) {
				setSelection(new StructuredSelection(node), true);
			} else {
				// reset the selection. client's selection listener should not be triggered.
				// there was only one selection on Expandable Node.
				Item[] curSel = tree.getSelection();
				if (curSel.length == 1) {
					tree.deselect((TreeItem) curSel[0]);
				}
			}
		} finally {
			tree.setRedraw(true);
			setBusy(oldBusy);
		}
	}

	/**
	 * Returns the data of the last item on the viewer.
	 *
	 * @param parent
	 *
	 * @return may return null
	 */
	Object getLastElement(Widget parent) {
		Item[] items = getChildren(parent);
		int length = items.length;
		if (length == 0) {
			return null;
		}
		return items[length - 1].getData();
	}
}
