/*******************************************************************************
 * Copyright (c) 2000, 2011 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
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.jarpackager;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Tree;

import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;

/**
 *	Combines a CheckboxTreeViewer and CheckboxListViewer.
 *	All viewer selection-driven interactions are handled within this viewer
 */
public class CheckboxTreeAndListGroup implements ICheckStateListener, ISelectionChangedListener, ITreeViewerListener {

	private Object fRoot;
	private Object fCurrentTreeSelection;
	private List<Object> fExpandedTreeNodes= new ArrayList<>();
	private Map<Object, List<Object>> fCheckedStateStore= new HashMap<>(9);
	private List<Object> fWhiteCheckedTreeItems= new ArrayList<>();
	private List<ICheckStateListener> fListeners= new ArrayList<>();

	private ITreeContentProvider fTreeContentProvider;
	private IStructuredContentProvider fListContentProvider;
	private ILabelProvider fTreeLabelProvider;
	private ILabelProvider fListLabelProvider;

	// widgets
	private CheckboxTreeViewer fTreeViewer;
	private CheckboxTableViewer fListViewer;

	/**
	 *	Creates an instance of this class.  Use this constructor if you wish to specify
	 *	the width and/or height of the combined widget (to only hardcode one of the
	 *	sizing dimensions, specify the other dimension's value as -1)
	 * @param parent parent composite
	 * @param rootObject
	 * @param treeContentProvider
	 * @param treeLabelProvider
	 * @param listContentProvider
	 * @param listLabelProvider
	 * @param style
	 * @param width the width
	 * @param height the height
	 */
	public CheckboxTreeAndListGroup(
			Composite parent,
			Object rootObject,
			ITreeContentProvider treeContentProvider,
			ILabelProvider treeLabelProvider,
			IStructuredContentProvider listContentProvider,
			ILabelProvider listLabelProvider,
			int style,
			int width,
			int height) {
		fRoot= rootObject;
		fTreeContentProvider= treeContentProvider;
		fListContentProvider= listContentProvider;
		fTreeLabelProvider= treeLabelProvider;
		fListLabelProvider= listLabelProvider;
		createContents(parent, width, height, style);
	}
	/**
	 * This method must be called just before this window becomes visible.
	 */
	public void aboutToOpen() {
		determineWhiteCheckedDescendents(fRoot);
		checkNewTreeElements(getTreeChildren(fRoot));
		fCurrentTreeSelection= null;

		//select the first element in the list
		Object[] elements= getTreeChildren(fRoot);
		Object primary= elements.length > 0 ? elements[0] : null;
		if (primary != null) {
			fTreeViewer.setSelection(new StructuredSelection(primary));
		}
		fTreeViewer.getControl().setFocus();
	}
	/**
	 *	Adds the passed listener to self's collection of clients
	 *	that listen for changes to element checked states
	 *
	 *	@param listener ICheckStateListener
	 */
	public void addCheckStateListener(ICheckStateListener listener) {
		fListeners.add(listener);
	}
	/**
	 * Adds the receiver and all of it's ancestors to the checkedStateStore if they
	 * are not already there.
	 * @param treeElement
	 */
	private void addToHierarchyToCheckedStore(Object treeElement) {

		// if this tree element is already gray then its ancestors all are as well
		if (!fCheckedStateStore.containsKey(treeElement))
			fCheckedStateStore.put(treeElement, new ArrayList<>());

		Object parent= fTreeContentProvider.getParent(treeElement);
		if (parent != null)
			addToHierarchyToCheckedStore(parent);
	}
	/**
	 *	Returns a boolean indicating whether all children of the passed tree element
	 *	are currently white-checked
	 *
	 *	@return boolean
	 *	@param treeElement java.lang.Object
	 */
	protected boolean areAllChildrenWhiteChecked(Object treeElement) {
		Object[] children= getTreeChildren(treeElement);
		return fWhiteCheckedTreeItems.containsAll(Arrays.asList(children));
	}
	/**
	 *	Returns a boolean indicating whether all list elements associated with
	 *	the passed tree element are currently checked
	 *
	 *	@return boolean
	 *	@param treeElement java.lang.Object
	 */
	protected boolean areAllElementsChecked(Object treeElement) {
		List<Object> checkedElements= fCheckedStateStore.get(treeElement);
		if (checkedElements == null) // ie.- tree item not even gray-checked
			return false;

		return getListItemsSize(treeElement) == checkedElements.size();
	}
	/**
	 *	Iterates through the passed elements which are being realized for the first
	 *	time and check each one in the tree viewer as appropriate
	 * @param elements
	 */
	protected void checkNewTreeElements(Object[] elements) {
		for (Object currentElement : elements) {
			boolean checked= fCheckedStateStore.containsKey(currentElement);
			fTreeViewer.setChecked(currentElement, checked);
			fTreeViewer.setGrayed(
				currentElement,
				checked && !fWhiteCheckedTreeItems.contains(currentElement));
		}
	}
	/**
	*	An item was checked in one of self's two views.  Determine which
	*	view this occurred in and delegate appropriately
	*
	*	@param event CheckStateChangedEvent
	*/
	@Override
	public void checkStateChanged(final CheckStateChangedEvent event) {

		//Potentially long operation - show a busy cursor
		BusyIndicator.showWhile(fTreeViewer.getControl().getDisplay(), () -> {
			if (event.getCheckable().equals(fTreeViewer))
				treeItemChecked(event.getElement(), event.getChecked());
			else
				listItemChecked(event.getElement(), event.getChecked(), true);

			notifyCheckStateChangeListeners(event);
		});
	}
	/**
	 *	Lay out and initialize self's visual components.
	 *
	 *	@param parent org.eclipse.swt.widgets.Composite
	 *	@param width int
	 *	@param height int
	 * @param style
	 */
	protected void createContents(
		Composite parent,
		int width,
		int height,
		int style) {
		// group pane
		Composite composite= new Composite(parent, style);
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.makeColumnsEqualWidth= true;
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));

		createTreeViewer(composite, width / 2, height);
		createListViewer(composite, width / 2, height);

		initialize();
	}
	/**
	 *	Creates this group's list viewer.
	 * @param parent the parent composite
	 * @param width the width
	 * @param height the height
	 */
	protected void createListViewer(Composite parent, int width, int height) {
		fListViewer= CheckboxTableViewer.newCheckList(parent, SWT.BORDER);
		fListViewer.setUseHashlookup(true);
		GridData data= new GridData(GridData.FILL_BOTH);
		data.widthHint= width;
		data.heightHint= height;
		fListViewer.getTable().setLayoutData(data);
		fListViewer.setContentProvider(fListContentProvider);
		fListViewer.setLabelProvider(fListLabelProvider);
		fListViewer.addCheckStateListener(this);
	}
	/**
	 *	Creates this group's tree viewer.
	 * @param parent parent composite
	 * @param width the width
	 * @param height the height
	 */
	protected void createTreeViewer(Composite parent, int width, int height) {
		Tree tree= new Tree(parent, SWT.CHECK | SWT.BORDER);
		GridData data= new GridData(GridData.FILL_BOTH);
		data.widthHint= width;
		data.heightHint= height;
		tree.setLayoutData(data);

		fTreeViewer= new CheckboxTreeViewer(tree);
		fTreeViewer.setUseHashlookup(true);
		fTreeViewer.setContentProvider(fTreeContentProvider);
		fTreeViewer.setLabelProvider(fTreeLabelProvider);
		fTreeViewer.addTreeListener(this);
		fTreeViewer.addCheckStateListener(this);
		fTreeViewer.addSelectionChangedListener(this);
	}
	/**
	 * Returns a boolean indicating whether the passed tree element should be
	 * at LEAST gray-checked.  Note that this method does not consider whether
	 * it should be white-checked, so a specified tree item which should be
	 * white-checked will result in a <code>true</code> answer from this method.
	 * To determine whether a tree item should be white-checked use method
	 * #determineShouldBeWhiteChecked(Object).
	 *
	 * @param treeElement java.lang.Object
	 * @return boolean
	 * @see #determineShouldBeWhiteChecked(java.lang.Object)
	 */
	protected boolean determineShouldBeAtLeastGrayChecked(Object treeElement) {
		// if any list items associated with treeElement are checked then it
		// retains its gray-checked status regardless of its children
		List<Object> checked= fCheckedStateStore.get(treeElement);
		if (checked != null && (!checked.isEmpty()))
			return true;

		// if any children of treeElement are still gray-checked then treeElement
		// must remain gray-checked as well
		Object[] children= getTreeChildren(treeElement);
		for (Object element : children) {
			if (fCheckedStateStore.containsKey(element))
				return true;
		}

		return false;
	}
	/**
	 * Returns a boolean indicating whether the passed tree item should be
	 * white-checked.
	 *
	 * @return boolean
	 * @param treeElement java.lang.Object
	 */
	protected boolean determineShouldBeWhiteChecked(Object treeElement) {
		return areAllChildrenWhiteChecked(treeElement)
			&& areAllElementsChecked(treeElement);
	}
	/**
	 *	Recursively adds appropriate tree elements to the collection of
	 *	known white-checked tree elements.
	 *
	 *	@param treeElement java.lang.Object
	 */
	protected void determineWhiteCheckedDescendents(Object treeElement) {
		// always go through all children first since their white-checked
		// statuses will be needed to determine the white-checked status for
		// this tree element
		Object[] children= getTreeChildren(treeElement);
		for (Object element : children)
			determineWhiteCheckedDescendents(element);

		// now determine the white-checked status for this tree element
		if (determineShouldBeWhiteChecked(treeElement))
			setWhiteChecked(treeElement, true);
	}
	/**
	 * Causes the tree viewer to expand all its items
	 */
	public void expandAll() {
		fTreeViewer.expandAll();
	}
	/**
	 *	Answers a flat collection of all of the checked elements in the
	 *	list portion of self
	 *
	 *	@return java.util.Vector
	 */
	public Iterator<Object> getAllCheckedListItems() {
		Set<Object> result= new HashSet<>();
		Iterator<List<Object>> listCollectionsEnum= fCheckedStateStore.values().iterator();
		while (listCollectionsEnum.hasNext())
			result.addAll(listCollectionsEnum.next());
		return result.iterator();
	}
	/**
	 *	Answer a collection of all of the checked elements in the tree portion
	 *	of self
	 *
	 *	@return java.util.Vector
	 */
	public Set<Object> getAllCheckedTreeItems() {
		return new HashSet<>(fCheckedStateStore.keySet());
	}
	/**
	 *	Answers the number of elements that have been checked by the
	 *	user.
	 *
	 *	@return int
	 */
	public int getCheckedElementCount() {
		return fCheckedStateStore.size();
	}
	/**
	 *	Returns a count of the number of list items associated with a
	 *	given tree item.
	 *
	 *	@return int
	 *	@param treeElement java.lang.Object
	 */
	protected int getListItemsSize(Object treeElement) {
		Object[] elements= getListElements(treeElement);
		return elements.length;
	}
	/**
	 * Gets the table that displays the folder content
	 *
	 * @return the table used to show the list
	 */
	public Table getTable() {
		return fListViewer.getTable();
	}
	/**
	 * Gets the tree that displays the list for a folder
	 *
	 * @return the tree used to show the folders
	 */
	public Tree getTree() {
		return fTreeViewer.getTree();
	}
	/**
	 * Adds the given filter to the tree viewer and
	 * triggers refiltering and resorting of the elements.
	 *
	 * @param filter a viewer filter
	 */
	public void addTreeFilter(ViewerFilter filter) {
		fTreeViewer.addFilter(filter);
	}
	/**
	 * Adds the given filter to the list viewer and
	 * triggers refiltering and resorting of the elements.
	 *
	 * @param filter a viewer filter
	 */
	public void addListFilter(ViewerFilter filter) {
		fListViewer.addFilter(filter);
	}
	/**
	 *	Logically gray-check all ancestors of treeItem by ensuring that they
	 *	appear in the checked table
	 * @param treeElement
	 */
	protected void grayCheckHierarchy(Object treeElement) {

		// if this tree element is already gray then its ancestors all are as well
		if (fCheckedStateStore.containsKey(treeElement))
			return; // no need to proceed upwards from here

		fCheckedStateStore.put(treeElement, new ArrayList<>());
		if (determineShouldBeWhiteChecked(treeElement)) {
			setWhiteChecked(treeElement, true);
		}
		Object parent= fTreeContentProvider.getParent(treeElement);
		if (parent != null)
			grayCheckHierarchy(parent);
	}
	/**
	 *	Sets the initial checked state of the passed list element to true.
	 * @param element
	 */
	public void initialCheckListItem(Object element) {
		Object parent= fTreeContentProvider.getParent(element);
		fCurrentTreeSelection= parent;
		//As this is not done from the UI then set the box for updating from the selection to false
		listItemChecked(element, true, false);
		updateHierarchy(parent);
	}
	/**
	 *	Sets the initial checked state of the passed element to true,
	 *	as well as to all of its children and associated list elements
	 * @param element
	 */
	public void initialCheckTreeItem(Object element) {
		treeItemChecked(element, true);
	}
	/**
	 *	Initializes this group's viewers after they have been laid out.
	 */
	protected void initialize() {
		fTreeViewer.setInput(fRoot);
	}
	/**
	 *	Callback that's invoked when the checked status of an item in the list
	 *	is changed by the user. Do not try and update the hierarchy if we are building the
	 *  initial list.
	 * @param listElement
	 * @param state
	 * @param updatingFromSelection
	 */
	protected void listItemChecked(
		Object listElement,
		boolean state,
		boolean updatingFromSelection) {
		List<Object> checkedListItems= fCheckedStateStore.get(fCurrentTreeSelection);

		if (state) {
			if (checkedListItems == null) {
				// since the associated tree item has gone from 0 -> 1 checked
				// list items, tree checking may need to be updated
				grayCheckHierarchy(fCurrentTreeSelection);
				checkedListItems= fCheckedStateStore.get(fCurrentTreeSelection);
			}
			checkedListItems.add(listElement);
		} else {
			checkedListItems.remove(listElement);
			if (checkedListItems.isEmpty()) {
				// since the associated tree item has gone from 1 -> 0 checked
				// list items, tree checking may need to be updated
				ungrayCheckHierarchy(fCurrentTreeSelection);
			}
		}

		if (updatingFromSelection)
			updateHierarchy(fCurrentTreeSelection);
	}
	/**
	 *	Notifies all checked state listeners that the passed element has had
	 *	its checked state changed to the passed state
	 * @param event
	 */
	protected void notifyCheckStateChangeListeners(CheckStateChangedEvent event) {
		Iterator<ICheckStateListener> listenersEnum= fListeners.iterator();
		while (listenersEnum.hasNext())
			 listenersEnum.next().checkStateChanged(event);
	}
	/**
	 *Sets the contents of the list viewer based upon the specified selected
	 *tree element.  This also includes checking the appropriate list items.
	 *
	 *@param treeElement java.lang.Object
	 */
	protected void populateListViewer(final Object treeElement) {
		if (treeElement == fCurrentTreeSelection)
			return;
		fCurrentTreeSelection= treeElement;
		fListViewer.setInput(treeElement);
		List<Object> listItemsToCheck= fCheckedStateStore.get(treeElement);

		if (listItemsToCheck != null) {
			Iterator<Object> listItemsEnum= listItemsToCheck.iterator();
			while (listItemsEnum.hasNext())
				fListViewer.setChecked(listItemsEnum.next(), true);
		}
	}
	/**
	 *	Removes the passed listener from self's collection of clients
	 *	that listen for changes to element checked states
	 *
	 *	@param listener ICheckStateListener
	 */
	public void removeCheckStateListener(ICheckStateListener listener) {
		fListeners.remove(listener);
	}
	/**
	 *	Handles the selection of an item in the tree viewer
	 *
	 *	@param event ISelection
	 */
	@Override
	public void selectionChanged(final SelectionChangedEvent event) {
		BusyIndicator.showWhile(getTable().getShell().getDisplay(), () -> {
			IStructuredSelection selection= (IStructuredSelection) event.getSelection();
			Object selectedElement= selection.getFirstElement();
			if (selectedElement == null) {
				fCurrentTreeSelection= null;
				fListViewer.setInput(fCurrentTreeSelection);
				return;
			}
			populateListViewer(selectedElement);
		});
	}

	/**
	 * Selects or deselect all of the elements in the tree depending on the value of the selection
	 * boolean. Be sure to update the displayed files as well.
	 * @param selection
	 */
	public void setAllSelections(final boolean selection) {

		//Potentially long operation - show a busy cursor
		BusyIndicator.showWhile(fTreeViewer.getControl().getDisplay(), () -> {
			setTreeChecked(fRoot, selection);
			fListViewer.setAllChecked(selection);
		});
	}

	/**
	 *	Sets the list viewer's providers to those passed
	 *
	 *	@param contentProvider ITreeContentProvider
	 *	@param labelProvider ILabelProvider
	 */
	public void setListProviders(
		IStructuredContentProvider contentProvider,
		ILabelProvider labelProvider) {
		fListViewer.setContentProvider(contentProvider);
		fListViewer.setLabelProvider(labelProvider);
	}
	/**
	 *	Sets the sorter that is to be applied to self's list viewer
	 * @param comparator
	 */
	public void setListComparator(ViewerComparator comparator) {
		fListViewer.setComparator(comparator);
	}
	/**
	 * Sets the root of the widget to be new Root. Regenerate all of the tables and lists from this
	 * value.
	 *
	 * @param newRoot
	 */
	public void setRoot(Object newRoot) {
		this.fRoot= newRoot;
		initialize();
	}
	/**
	 *	Sets the checked state of the passed tree element appropriately, and
	 *	do so recursively to all of its child tree elements as well
	 * @param treeElement
	 * @param state
	 */
	protected void setTreeChecked(Object treeElement, boolean state) {

		if (treeElement.equals(fCurrentTreeSelection)) {
			fListViewer.setAllChecked(state);
		}

		if (state) {
			Object[] listItems= getListElements(treeElement);
			List<Object> listItemsChecked= new ArrayList<>();
			Collections.addAll(listItemsChecked, listItems);

			fCheckedStateStore.put(treeElement, listItemsChecked);
		} else
			fCheckedStateStore.remove(treeElement);

		setWhiteChecked(treeElement, state);
		fTreeViewer.setChecked(treeElement, state);
		fTreeViewer.setGrayed(treeElement, false);

		// now logically check/uncheck all children as well
		Object[] children= getTreeChildren(treeElement);
		for (Object child : children) {
			setTreeChecked(child, state);
		}
	}
	/**
	 *	Sets the tree viewer's providers to those passed
	 *
	 *	@param contentProvider ITreeContentProvider
	 *	@param labelProvider ILabelProvider
	 */
	public void setTreeProviders(
		ITreeContentProvider contentProvider,
		ILabelProvider labelProvider) {
		fTreeViewer.setContentProvider(contentProvider);
		fTreeViewer.setLabelProvider(labelProvider);
	}
	/**
	 *	Sets the sorter that is to be applied to self's tree viewer
	 * @param sorter
	 */
	public void setTreeComparator(ViewerComparator sorter) {
		fTreeViewer.setComparator(sorter);
	}
	/**
	 *	Adjusts the collection of references to white-checked tree elements appropriately.
	 *
	 *	@param treeElement java.lang.Object
	 *	@param isWhiteChecked boolean
	 */
	protected void setWhiteChecked(Object treeElement, boolean isWhiteChecked) {
		if (isWhiteChecked) {
			if (!fWhiteCheckedTreeItems.contains(treeElement))
				fWhiteCheckedTreeItems.add(treeElement);
		} else
			fWhiteCheckedTreeItems.remove(treeElement);
	}
	/**
	 *	Handle the collapsing of an element in a tree viewer
	 * @param event
	 */
	@Override
	public void treeCollapsed(TreeExpansionEvent event) {
		// We don't need to do anything with this
	}

	/**
	 *	Handles the expansionsion of an element in a tree viewer
	 * @param event
	 */
	@Override
	public void treeExpanded(TreeExpansionEvent event) {

		Object item= event.getElement();

		// First see if the children need to be given their checked state at all.  If they've
		// already been realized then this won't be necessary
		if (!fExpandedTreeNodes.contains(item)) {
			fExpandedTreeNodes.add(item);
			checkNewTreeElements(getTreeChildren(item));
		}
	}

	/**
	 *  Callback that's invoked when the checked status of an item in the tree
	 *  is changed by the user.
	 * @param treeElement
	 * @param state
	 */
	protected void treeItemChecked(Object treeElement, boolean state) {

		// recursively adjust all child tree elements appropriately
		setTreeChecked(treeElement, state);

		Object parent= fTreeContentProvider.getParent(treeElement);
		if (parent == null)
			return;

		// now update upwards in the tree hierarchy
		if (state)
			grayCheckHierarchy(parent);
		else
			ungrayCheckHierarchy(parent);

		updateHierarchy(treeElement);
	}
	/**
	 *	Logically un-gray-check all ancestors of treeItem iff appropriate.
	 * @param treeElement
	 */
	protected void ungrayCheckHierarchy(Object treeElement) {
		if (!determineShouldBeAtLeastGrayChecked(treeElement))
			fCheckedStateStore.remove(treeElement);

		Object parent= fTreeContentProvider.getParent(treeElement);
		if (parent != null)
			ungrayCheckHierarchy(parent);
	}
	/**
	 *	Sets the checked state of self and all ancestors appropriately
	 * @param treeElement
	 */
	protected void updateHierarchy(Object treeElement) {

		boolean whiteChecked= determineShouldBeWhiteChecked(treeElement);
		boolean shouldBeAtLeastGray= determineShouldBeAtLeastGrayChecked(treeElement);

		fTreeViewer.setChecked(treeElement, whiteChecked || shouldBeAtLeastGray);
		setWhiteChecked(treeElement, whiteChecked);
		if (whiteChecked)
			fTreeViewer.setGrayed(treeElement, false);
		else
			fTreeViewer.setGrayed(treeElement, shouldBeAtLeastGray);

		// proceed up the tree element hierarchy
		Object parent= fTreeContentProvider.getParent(treeElement);
		if (parent != null) {
			updateHierarchy(parent);
		}
	}
	/**
	 * Update the selections of the tree elements in items to reflect the new
	 * selections provided.
	 *
	 * @param items with keys of Object (the tree element) and values of List (the selected
	 * list elements).
	 */
	public void updateSelections(final Map<Object, List<Object>> items) {

		//Potentially long operation - show a busy cursor
		BusyIndicator.showWhile(fTreeViewer.getControl().getDisplay(), () -> handleUpdateSelection(items));
	}
	/**
	 * Returns the result of running the given elements through the filters.
	 * @param filters
	 *
	 * @param elements the elements to filter
	 * @return only the elements which all filters accept
	 */
	protected Object[] filter(ViewerFilter[] filters, Object[] elements) {
		if (filters != null) {
			ArrayList<Object> filtered = new ArrayList<>(elements.length);
			for (Object element : elements) {
				boolean add = true;
				for (ViewerFilter filter : filters) {
					add = filter.select(null, null, element);
					if (!add)
						break;
				}
				if (add) {
					filtered.add(element);
				}
			}
			return filtered.toArray();
		}
		return elements;
	}

	private Object[] getTreeChildren(Object element) {
		return filter(fTreeViewer.getFilters(), fTreeContentProvider.getChildren(element));
	}

	private Object[] getListElements(Object element) {
		return filter(fListViewer.getFilters(), fListContentProvider.getElements(element));
	}

	public Set<Object> getWhiteCheckedTreeItems() {
		return new HashSet<>(fWhiteCheckedTreeItems);
	}

	private void handleUpdateSelection(Map<Object, List<Object>> items) {
		Iterator<Object> keyIterator= items.keySet().iterator();

		//Update the store before the hierarchy to prevent updating parents before all of the children are done
		while (keyIterator.hasNext()) {
			Object key= keyIterator.next();
			//Replace the items in the checked state store with those from the supplied items
			List<Object> selections= items.get(key);
			if (selections.isEmpty())
				//If it is empty remove it from the list
				fCheckedStateStore.remove(key);
			else {
				fCheckedStateStore.put(key, selections);
				// proceed up the tree element hierarchy
				Object parent= fTreeContentProvider.getParent(key);
				if (parent != null) {
					addToHierarchyToCheckedStore(parent);
				}
			}
		}

		//Now update hierarchies
		keyIterator= items.keySet().iterator();

		while (keyIterator.hasNext()) {
			Object key= keyIterator.next();
			updateHierarchy(key);
			if (fCurrentTreeSelection != null && fCurrentTreeSelection.equals(key)) {
				fListViewer.setAllChecked(false);
				fListViewer.setCheckedElements(items.get(key).toArray());
			}
		}
	}

	/**
	 * Checks if an element is grey checked.
	 * @param object
	 * @return if an element is grey checked.
	 */
	public boolean isTreeItemGreyChecked(Object object) {
		return fTreeViewer.getGrayed(object);
	}

	/**
	 * For a given element, expand its chidren to a level.
	 * @param object
	 * @param level
	 */
	public void expandTreeToLevel(Object object, int level) {
		fTreeViewer.expandToLevel(object, level);
	}
	/**
	 * @param selection
	 */
	public void setTreeSelection(ISelection selection) {
		fTreeViewer.setSelection(selection);
	}
}
