/*******************************************************************************
 * Copyright (c) 2000, 2015 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> - initial API and implementation bug 154329
 *                                               - fixes in bug 170381, 198665, 200731
 *******************************************************************************/

package org.eclipse.jface.viewers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Widget;

/**
 * This is a widget independent class implementors of
 * {@link org.eclipse.swt.widgets.Table} like widgets can use to provide a
 * viewer on top of their widget implementations.
 * <p>
 * <strong> This class is not intended to be subclassed outside of the JFace
 * viewers framework.</strong>
 * </p>
 *
 * @since 3.3
 */
public abstract class AbstractTableViewer extends ColumnViewer {

	private class VirtualManager {

		/**
		 * The currently invisible elements as provided by the content provider
		 * or by addition. This will not be populated by an
		 * ILazyStructuredContentProvider as an ILazyStructuredContentProvider
		 * is only queried on the virtual callback.
		 */
		private Object[] cachedElements = new Object[0];

		/**
		 * Create a new instance of the receiver.
		 *
		 */
		public VirtualManager() {
			addTableListener();
		}

		/**
		 * Add the listener for SetData on the table
		 */
		private void addTableListener() {
			getControl().addListener(SWT.SetData, event -> {
				Item item = (Item) event.item;
				final int index = doIndexOf(item);

				if (index == -1) {
					// Should not happen, but the spec for doIndexOf allows returning -1.
					// See bug 241117.
					return;
				}

				Object element = resolveElement(index);
				if (element == null) {
					// Didn't find it so make a request
					// Keep looking if it is not in the cache.
					IContentProvider contentProvider = getContentProvider();
					// If we are building lazily then request lookup now
					if (contentProvider instanceof ILazyContentProvider) {
						ILazyContentProvider lazyProvider = (ILazyContentProvider) contentProvider;
						if (!isBusy()) {
							lazyProvider.updateElement(index);
						} else {
							// In case event is sent during doUpdateItem() we
							// should run async update to avoid RuntimeException
							// from ColumnViewer.checkBusy(), see bug 488484
							Control control = getControl();
							control.getDisplay().asyncExec(() -> {
								if (!control.isDisposed()) {
									lazyProvider.updateElement(index);
								}
							});
						}
						return;
					}
				}

				associate(element, item);
				updateItem(item, element);
			});
		}

		/**
		 * Get the element at index.Resolve it lazily if this is available.
		 *
		 * @param index
		 * @return Object or <code>null</code> if it could not be found
		 */
		protected Object resolveElement(int index) {

			Object element = null;
			if (index < cachedElements.length) {
				element = cachedElements[index];
			}

			return element;
		}

		/**
		 * A non visible item has been added.
		 *
		 * @param element
		 * @param index
		 */
		public void notVisibleAdded(Object element, int index) {

			int requiredCount = doGetItemCount() + 1;

			Object[] newCache = new Object[requiredCount];
			System.arraycopy(cachedElements, 0, newCache, 0, index);
			if (index < cachedElements.length) {
				System.arraycopy(cachedElements, index, newCache, index + 1,
						cachedElements.length - index);
			}
			newCache[index] = element;
			cachedElements = newCache;

			doSetItemCount(requiredCount);
		}

		/**
		 * The elements with the given indices need to be removed from the
		 * cache.
		 *
		 * @param indices
		 */
		public void removeIndices(int[] indices) {
			if (indices.length == 1) {
				removeIndicesFromTo(indices[0], indices[0]);
			}
			int requiredCount = doGetItemCount() - indices.length;

			Arrays.sort(indices);
			Object[] newCache = new Object[requiredCount];
			int indexInNewCache = 0;
			int nextToSkip = 0;
			for (int i = 0; i < cachedElements.length; i++) {
				if (nextToSkip < indices.length && i == indices[nextToSkip]) {
					nextToSkip++;
				} else {
					newCache[indexInNewCache++] = cachedElements[i];
				}
			}
			cachedElements = newCache;
		}

		/**
		 * The elements between the given indices (inclusive) need to be removed
		 * from the cache.
		 *
		 * @param from
		 * @param to
		 */
		public void removeIndicesFromTo(int from, int to) {
			int indexAfterTo = to + 1;
			Object[] newCache = new Object[cachedElements.length
					- (indexAfterTo - from)];
			System.arraycopy(cachedElements, 0, newCache, 0, from);
			if (indexAfterTo < cachedElements.length) {
				System.arraycopy(cachedElements, indexAfterTo, newCache, from,
						cachedElements.length - indexAfterTo);
			}
		}

		/**
		 * @param element
		 * @return the index of the element in the cache, or null
		 */
		public int find(Object element) {
			return Arrays.asList(cachedElements).indexOf(element);
		}

		/**
		 * @param count
		 */
		public void adjustCacheSize(int count) {
			if (count == cachedElements.length) {
				return;
			} else if (count < cachedElements.length) {
				Object[] newCache = new Object[count];
				System.arraycopy(cachedElements, 0, newCache, 0, count);
				cachedElements = newCache;
			} else {
				Object[] newCache = new Object[count];
				System.arraycopy(cachedElements, 0, newCache, 0,
						cachedElements.length);
				cachedElements = newCache;
			}
		}

	}

	private VirtualManager virtualManager;

	/**
	 * Create the new viewer for table like widgets
	 */
	public AbstractTableViewer() {
		super();
	}

	@Override
	protected void hookControl(Control control) {
		super.hookControl(control);
		initializeVirtualManager(getControl().getStyle());
	}

	@Override
	protected void handleDispose(DisposeEvent event) {
		super.handleDispose(event);
		virtualManager = null;
	}

	/**
	 * Initialize the virtual manager to manage the virtual state if the table
	 * is VIRTUAL. If not use the default no-op version.
	 *
	 * @param style
	 */
	private void initializeVirtualManager(int style) {
		if ((style & SWT.VIRTUAL) == 0) {
			return;
		}

		virtualManager = new VirtualManager();
	}

	/**
	 * Adds the given elements to this table viewer. If this viewer does not
	 * have a sorter, the elements are added at the end in the order given;
	 * otherwise the elements are inserted at appropriate positions.
	 * <p>
	 * This method should be called (by the content provider) when elements have
	 * been added to the model, in order to cause the viewer to accurately
	 * reflect the model. This method only affects the viewer, not the model.
	 * </p>
	 *
	 * @param elements
	 *            the elements to add
	 */
	public void add(Object[] elements) {
		assertElementsNotNull(elements);
		if (checkBusy())
			return;
		Object[] filtered = filter(elements);

		for (Object element : filtered) {
			int index = indexForElement(element);
			createItem(element, index);
		}
	}

	/**
	 * Create a new TableItem at index if required.
	 *
	 * @param element
	 * @param index
	 *
	 * @since 3.1
	 */
	private void createItem(Object element, int index) {
		if (virtualManager == null) {
			updateItem(internalCreateNewRowPart(SWT.NONE, index).getItem(),
					element);
		} else {
			virtualManager.notVisibleAdded(element, index);

		}
	}

	/**
	 * Create a new row.  Callers can only use the returned object locally and before
	 * making the next call on the viewer since it may be re-used for subsequent method
	 * calls.
	 *
	 * @param style
	 *            the style for the new row
	 * @param rowIndex
	 *            the index of the row or -1 if the row is appended at the end
	 * @return the newly created row
	 */
	protected abstract ViewerRow internalCreateNewRowPart(int style,
			int rowIndex);

	/**
	 * Adds the given element to this table viewer. If this viewer does not have
	 * a sorter, the element is added at the end; otherwise the element is
	 * inserted at the appropriate position.
	 * <p>
	 * This method should be called (by the content provider) when a single
	 * element has been added to the model, in order to cause the viewer to
	 * accurately reflect the model. This method only affects the viewer, not
	 * the model. Note that there is another method for efficiently processing
	 * the simultaneous addition of multiple elements.
	 * </p>
	 *
	 * @param element
	 *            the element to add
	 */
	public void add(Object element) {
		add(new Object[] { element });
	}

	@Override
	protected Widget doFindInputItem(Object element) {
		if (equals(element, getRoot())) {
			return getControl();
		}
		return null;
	}

	@Override
	protected Widget doFindItem(Object element) {

		Item[] children = doGetItems();
		for (Item item : children) {
			Object data = item.getData();
			if (data != null && equals(data, element)) {
				return item;
			}
		}

		return null;
	}

	@Override
	protected void doUpdateItem(Widget widget, Object element, boolean fullMap) {
		boolean oldBusy = isBusy();
		setBusy(true);
		try {
			if (widget instanceof Item) {
				final Item item = (Item) widget;

				// remember element we are showing
				if (fullMap) {
					associate(element, item);
				} else {
					Object data = item.getData();
					if (data != null) {
						unmapElement(data, item);
					}
					item.setData(element);
					mapElement(element, item);
				}

				int columnCount = doGetColumnCount();
				if (columnCount == 0)
					columnCount = 1;// If there are no columns do the first one

				ViewerRow viewerRowFromItem = getViewerRowFromItem(item);

				boolean isVirtual = (getControl().getStyle() & SWT.VIRTUAL) != 0;

				// If the control is virtual, we cannot use the cached viewer row object. See bug 188663.
				if (isVirtual) {
					viewerRowFromItem = (ViewerRow) viewerRowFromItem.clone();
				}

				// Also enter loop if no columns added. See 1G9WWGZ: JFUIF:WINNT -
				// TableViewer with 0 columns does not work
				for (int column = 0; column < columnCount || column == 0; column++) {
					ViewerColumn columnViewer = getViewerColumn(column);
					ViewerCell cellToUpdate = updateCell(viewerRowFromItem,
							column, element);

					// If the control is virtual, we cannot use the cached cell object. See bug 188663.
					if (isVirtual) {
						cellToUpdate = new ViewerCell(cellToUpdate.getViewerRow(), cellToUpdate.getColumnIndex(), element);
					}

					columnViewer.refresh(cellToUpdate);

					// clear cell (see bug 201280)
					updateCell(null, 0, null);

					// As it is possible for user code to run the event
					// loop check here.
					if (item.isDisposed()) {
						unmapElement(element, item);
						return;
					}

				}

			}
		} finally {
			setBusy(oldBusy);
		}
	}

	@Override
	protected Widget getColumnViewerOwner(int columnIndex) {
		int columnCount = doGetColumnCount();

		if (columnIndex < 0
				|| (columnIndex > 0 && columnIndex >= columnCount)) {
			return null;
		}

		if (columnCount == 0)// Hang it off the table if it
			return getControl();

		return doGetColumn(columnIndex);
	}

	/**
	 * Returns the element with the given index from this table viewer. Returns
	 * <code>null</code> if the index is out of range.
	 * <p>
	 * This method is internal to the framework.
	 * </p>
	 *
	 * @param index
	 *            the zero-based index
	 * @return the element at the given index, or <code>null</code> if the
	 *         index is out of range
	 */
	public Object getElementAt(int index) {
		if (index >= 0 && index < doGetItemCount()) {
			Item i = doGetItem(index);
			if (i != null) {
				return i.getData();
			}
		}
		return null;
	}

	/**
	 * The table viewer implementation of this <code>Viewer</code> framework
	 * method returns the label provider, which in the case of table viewers
	 * will be 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 List getSelectionFromWidget() {
		if (virtualManager != null) {
			return getVirtualSelection();
		}
		Widget[] items = doGetSelection();
		ArrayList list = new ArrayList(items.length);
		for (Widget item : items) {
			Object e = item.getData();
			if (e != null) {
				list.add(e);
			}
		}
		return list;
	}

	/**
	 * Get the virtual selection. Avoid calling SWT whenever possible to prevent
	 * extra widget creation.
	 *
	 * @return List of Object
	 */

	private List getVirtualSelection() {

		List result = new ArrayList();
		int[] selectionIndices = doGetSelectionIndices();
		if (getContentProvider() instanceof ILazyContentProvider) {
			ILazyContentProvider lazy = (ILazyContentProvider) getContentProvider();
			for (int selectionIndex : selectionIndices) {
				lazy.updateElement(selectionIndex);// Start the update
				// check for the case where the content provider changed the number of items
				if (selectionIndex < doGetItemCount()) {
					Object element = doGetItem(selectionIndex).getData();
					// Only add the element if it got updated.
					// If this is done deferred the selection will
					// be incomplete until selection is finished.
					if (element != null) {
						result.add(element);
					}
				}
			}
		} else {
			for (int selectionIndex : selectionIndices) {
				Object element = null;
				if (selectionIndex < virtualManager.cachedElements.length) {
					element = virtualManager.cachedElements[selectionIndex];
				}
				if (element == null) {
					// Not cached so try the item's data
					Item item = doGetItem(selectionIndex);
					element = item.getData();
				}
				if (element != null) {
					result.add(element);
				}
			}

		}
		return result;
	}

	/**
	 * @param element
	 *            the element to insert
	 * @return the index where the item should be inserted.
	 */
	protected int indexForElement(Object element) {
		ViewerComparator comparator = getComparator();
		if (comparator == null) {
			return doGetItemCount();
		}
		int count = doGetItemCount();
		int min = 0, max = count - 1;
		while (min <= max) {
			int mid = (min + max) / 2;
			Object data = doGetItem(mid).getData();
			int compare = comparator.compare(this, data, element);
			if (compare == 0) {
				// find first item > element
				while (compare == 0) {
					++mid;
					if (mid >= count) {
						break;
					}
					data = doGetItem(mid).getData();
					compare = comparator.compare(this, data, element);
				}
				return mid;
			}
			if (compare < 0) {
				min = mid + 1;
			} else {
				max = mid - 1;
			}
		}
		return min;
	}

	@Override
	protected void inputChanged(Object input, Object oldInput) {
		getControl().setRedraw(false);
		try {
			preservingSelection(() -> internalRefresh(getRoot()));
		} finally {
			getControl().setRedraw(true);
		}
	}

	/**
	 * Inserts the given element into this table viewer at the given position.
	 * If this viewer has a sorter, the position is ignored and the element is
	 * inserted at the correct position in the sort order.
	 * <p>
	 * This method should be called (by the content provider) when elements have
	 * been added to the model, in order to cause the viewer to accurately
	 * reflect the model. This method only affects the viewer, not the model.
	 * </p>
	 *
	 * @param element
	 *            the element
	 * @param position
	 *            a 0-based position relative to the model, or -1 to indicate
	 *            the last position
	 */
	public void insert(Object element, int position) {
		applyEditorValue();
		if (getComparator() != null || hasFilters()) {
			add(element);
			return;
		}
		if (position == -1) {
			position = doGetItemCount();
		}
		if (checkBusy())
			return;
		createItem(element, position);
	}

	@Override
	protected void internalRefresh(Object element) {
		internalRefresh(element, true);
	}

	@Override
	protected void internalRefresh(Object element, boolean updateLabels) {
		applyEditorValue();
		if (element == null || equals(element, getRoot())) {
			if (virtualManager == null) {
				internalRefreshAll(updateLabels);
			} else {
				internalVirtualRefreshAll();
			}
		} else {
			Widget w = findItem(element);
			if (w != null) {
				updateItem(w, element);
			}
		}
	}

	/**
	 * Refresh all with virtual elements.
	 *
	 * @since 3.1
	 */
	private void internalVirtualRefreshAll() {

		Object root = getRoot();
		IContentProvider contentProvider = getContentProvider();

		// Invalidate for lazy
		if (!(contentProvider instanceof ILazyContentProvider)
				&& (contentProvider instanceof IStructuredContentProvider)) {
			// Don't cache if the root is null but cache if it is not lazy.
			if (root != null) {
				virtualManager.cachedElements = getSortedChildren(root);
				doSetItemCount(virtualManager.cachedElements.length);
			}
		}
		doClearAll();
	}

	/**
	 * Refresh all of the elements of the table. update the labels if
	 * updatLabels is true;
	 *
	 * @param updateLabels
	 *
	 * @since 3.1
	 */
	private void internalRefreshAll(boolean updateLabels) {
		// the parent

		// in the code below, it is important to do all disassociates
		// before any associates, since a later disassociate can undo an
		// earlier associate
		// e.g. if (a, b) is replaced by (b, a), the disassociate of b to
		// item 1 could undo
		// the associate of b to item 0.

		Object[] children = getSortedChildren(getRoot());
		Item[] items = doGetItems();
		int min = Math.min(children.length, items.length);
		for (int i = 0; i < min; ++i) {

			Item item = items[i];

			// if the element is unchanged, update its label if appropriate
			if (equals(children[i], item.getData())) {
				if (updateLabels) {
					updateItem(item, children[i]);
				} else {
					// associate the new element, even if equal to the old
					// one,
					// to remove stale references (see bug 31314)
					associate(children[i], item);
				}
			} else {
				// updateItem does an associate(...), which can mess up
				// the associations if the order of elements has changed.
				// E.g. (a, b) -> (b, a) first replaces a->0 with b->0, then
				// replaces b->1 with a->1, but this actually removes b->0.
				// So, if the object associated with this item has changed,
				// just disassociate it for now, and update it below.
				// we also need to reset the item (set its text,images etc. to
				// default values) because the label decorators rely on this
				disassociate(item);
				doClear(i);
			}
		}
		// dispose of all items beyond the end of the current elements
		if (min < items.length) {
			for (int i = items.length; --i >= min;) {

				disassociate(items[i]);
			}
			if (virtualManager != null) {
				virtualManager.removeIndicesFromTo(min, items.length - 1);
			}
			doRemove(min, items.length - 1);
		}
		// Workaround for 1GDGN4Q: ITPUI:WIN2000 - TableViewer icons get
		// scrunched
		if (doGetItemCount() == 0) {
			doRemoveAll();
		}
		// Update items which were disassociated above
		for (int i = 0; i < min; ++i) {

			Item item = items[i];
			if (item.getData() == null) {
				updateItem(item, children[i]);
			}
		}
		// add any remaining elements
		for (int i = min; i < children.length; ++i) {
			createItem(children[i], i);
		}
	}

	/**
	 * Removes the given elements from this table viewer.
	 *
	 * @param elements
	 *            the elements to remove
	 */
	private void internalRemove(final Object[] elements) {
		Object input = getInput();
		for (Object element : elements) {
			if (equals(element, input)) {
				boolean oldBusy = isBusy();
				setBusy(false);
				try {
					setInput(null);
				} finally {
					setBusy(oldBusy);
				}
				return;
			}
		}
		// use remove(int[]) rather than repeated TableItem.dispose() calls
		// to allow SWT to optimize multiple removals
		int[] indices = new int[elements.length];
		int count = 0;
		for (Object element : elements) {
			Widget w = findItem(element);
			if (w == null && virtualManager != null) {
				int index = virtualManager.find(element);
				if (index != -1) {
					indices[count++] = index;
				}
			} else if (w instanceof Item) {
				Item item = (Item) w;
				disassociate(item);
				indices[count++] = doIndexOf(item);
			}
		}
		if (count < indices.length) {
			System.arraycopy(indices, 0, indices = new int[count], 0, count);
		}
		if (virtualManager != null) {
			virtualManager.removeIndices(indices);
		}
		doRemove(indices);

		// Workaround for 1GDGN4Q: ITPUI:WIN2000 - TableViewer icons get
		// scrunched
		if (doGetItemCount() == 0) {
			doRemoveAll();
		}
	}

	/**
	 * Removes the given elements from this table viewer. The selection is
	 * updated if required.
	 * <p>
	 * This method should be called (by the content provider) when elements have
	 * been removed from the model, in order to cause the viewer to accurately
	 * reflect the model. This method only affects the viewer, not the model.
	 * </p>
	 *
	 * @param elements
	 *            the elements to remove
	 */
	public void remove(final Object[] elements) {
		assertElementsNotNull(elements);
		if (checkBusy())
			return;
		if (elements.length == 0) {
			return;
		}
		preservingSelection(() -> internalRemove(elements));
	}

	/**
	 * Removes the given element from this table viewer. The selection is
	 * updated if necessary.
	 * <p>
	 * This method should be called (by the content provider) when a single
	 * element has been removed from the model, in order to cause the viewer to
	 * accurately reflect the model. This method only affects the viewer, not
	 * the model. Note that there is another method for efficiently processing
	 * the simultaneous removal of multiple elements.
	 * </p>
	 * <strong>NOTE:</strong> removing an object from a virtual table will
	 * decrement the itemCount.
	 *
	 * @param element
	 *            the element
	 */
	public void remove(Object element) {
		remove(new Object[] { element });
	}

	@Override
	public void reveal(Object element) {
		Assert.isNotNull(element);
		Widget w = findItem(element);
		if (w instanceof Item) {
			doShowItem((Item) w);
		}
	}

	@Override
	protected void setSelectionToWidget(List list, boolean reveal) {
		if (list == null) {
			doDeselectAll();
			return;
		}

		if (virtualManager != null) {
			virtualSetSelectionToWidget(list, reveal);
			return;
		}

		// This is vital to use doSetSelection because on SWT-Table on Win32 this will also
		// move the focus to this row (See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=198665)
		if (reveal) {
			int size = list.size();
			Item[] items = new Item[size];
			int count = 0;
			for (int i = 0; i < size; ++i) {
				Object o = list.get(i);
				Widget w = findItem(o);
				if (w instanceof Item) {
					Item item = (Item) w;
					items[count++] = item;
				}
			}
			if (count < size) {
				System.arraycopy(items, 0, items = new Item[count], 0, count);
			}
			doSetSelection(items);
		} else {
			doDeselectAll(); // Clear the selection
			if( ! list.isEmpty() ) {
				int[] indices = new int[list.size()];

				Iterator it = list.iterator();
				Item[] items = doGetItems();
				Object modelElement;

				int count = 0;
				while( it.hasNext() ) {
					modelElement = it.next();
					boolean found = false;
					for (int i = 0; i < items.length && !found; i++) {
						if (equals(modelElement, items[i].getData())) {
							indices[count++] = i;
							found = true;
						}
					}
				}

				if (count < indices.length) {
					System.arraycopy(indices, 0, indices = new int[count], 0, count);
				}

				doSelect(indices);
			}
		}
	}

	/**
	 * Set the selection on a virtual table
	 *
	 * @param list
	 *            The elements to set
	 * @param reveal
	 *            Whether or not reveal the first item.
	 */
	private void virtualSetSelectionToWidget(List list, boolean reveal) {
		int size = list.size();
		int[] indices = new int[list.size()];

		Item firstItem = null;
		int count = 0;
		HashSet virtualElements = new HashSet();
		for (int i = 0; i < size; ++i) {
			Object o = list.get(i);
			Widget w = findItem(o);
			if (w instanceof Item) {
				Item item = (Item) w;
				indices[count++] = doIndexOf(item);
				if (firstItem == null) {
					firstItem = item;
				}
			} else {
				virtualElements.add(o);
			}
		}

		if (getContentProvider() instanceof ILazyContentProvider) {
			ILazyContentProvider provider = (ILazyContentProvider) getContentProvider();

			// Now go through it again until all is done or we are no longer
			// virtual
			// This may create all items so it is not a good
			// idea in general.
			// Use #setSelection (int [] indices,boolean reveal) instead
			for (int i = 0; virtualElements.size() > 0 && i < doGetItemCount(); i++) {
				provider.updateElement(i);
				Item item = doGetItem(i);
				if (virtualElements.contains(item.getData())) {
					indices[count++] = i;
					virtualElements.remove(item.getData());
					if (firstItem == null) {
						firstItem = item;
					}
				}
			}
		} else {

			if (count != list.size()) {// As this is expensive skip it if all
				// have been found
				// If it is not lazy we can use the cache
				for (int i = 0; i < virtualManager.cachedElements.length; i++) {
					Object element = virtualManager.cachedElements[i];
					if (virtualElements.contains(element)) {
						Item item = doGetItem(i);
						item.getText();// Be sure to fire the update
						indices[count++] = i;
						virtualElements.remove(element);
						if (firstItem == null) {
							firstItem = item;
						}
					}
				}
			}
		}

		if (count < size) {
			System.arraycopy(indices, 0, indices = new int[count], 0, count);
		}

		if (reveal) {
			doSetSelection(indices);
		} else {
			doDeselectAll();
			doSelect(indices);
		}

	}

	/**
	 * Set the item count of the receiver.
	 *
	 * @param count
	 *            the new table size.
	 *
	 * @since 3.1
	 */
	public void setItemCount(int count) {
		if (checkBusy())
			return;
		int oldCount = doGetItemCount();
		if (count < oldCount) {
			// need to disassociate elements that are being disposed
			for (int i = count; i < oldCount; i++) {
				Item item = doGetItem(i);
				if (item.getData() != null) {
					disassociate(item);
				}
			}
		}
		doSetItemCount(count);
		if (virtualManager != null) {
			virtualManager.adjustCacheSize(count);
		}
		getControl().redraw();
	}

	/**
	 * Replace the element at the given index with the given element. This
	 * method will not call the content provider to verify. <strong>Note that
	 * this method will materialize a TableItem the given index.</strong>.
	 *
	 * @param element
	 * @param index
	 * @see ILazyContentProvider
	 *
	 * @since 3.1
	 */
	public void replace(Object element, int index) {
		if (checkBusy())
			return;
		Item item = doGetItem(index);
		refreshItem(item, element);
	}

	/**
	 * Clear the table item at the specified index
	 *
	 * @param index
	 *            the index of the table item to be cleared
	 *
	 * @since 3.1
	 */
	public void clear(int index) {
		Item item = doGetItem(index);
		if (item.getData() != null) {
			disassociate(item);
		}
		doClear(index);
	}

	@Override
	protected Object[] getRawChildren(Object parent) {

		Assert.isTrue(!(getContentProvider() instanceof ILazyContentProvider),
				"Cannot get raw children with an ILazyContentProvider");//$NON-NLS-1$
		return super.getRawChildren(parent);

	}

	/**
	 * Sets the content provider used by this <code>AbstractTableViewer</code>.
	 * <p>
	 * Content providers for abstract table viewers must implement either
	 * {@link IStructuredContentProvider} or {@link ILazyContentProvider}.
	 */
	@Override
	public void setContentProvider(IContentProvider provider) {
		// the actual check is in assertContentProviderType
		super.setContentProvider(provider);
	}

	@Override
	protected void assertContentProviderType(IContentProvider provider) {
		Assert.isTrue(provider instanceof IStructuredContentProvider
				|| provider instanceof ILazyContentProvider);
	}

	/**
	 * Searches the receiver's list starting at the first item (index 0) until
	 * an item is found that is equal to the argument, and returns the index of
	 * that item. If no item is found, returns -1.
	 *
	 * @param item
	 *            the search item
	 * @return the index of the item
	 *
	 * @since 3.3
	 */
	protected abstract int doIndexOf(Item item);

	/**
	 * Returns the number of items contained in the receiver.
	 *
	 * @return the number of items
	 *
	 * @since 3.3
	 */
	protected abstract int doGetItemCount();

	/**
	 * Sets the number of items contained in the receiver.
	 *
	 * @param count
	 *            the number of items
	 *
	 * @since 3.3
	 */
	protected abstract void doSetItemCount(int count);

	/**
	 * Returns a (possibly empty) array of TableItems which are the items in the
	 * receiver.
	 *
	 * @return the items in the receiver
	 *
	 * @since 3.3
	 */
	protected abstract Item[] doGetItems();

	/**
	 * Returns the column at the given, zero-relative index in the receiver.
	 * Throws an exception if the index is out of range. Columns are returned in
	 * the order that they were created. If no TableColumns were created by the
	 * programmer, this method will throw ERROR_INVALID_RANGE despite the fact
	 * that a single column of data may be visible in the table. This occurs
	 * when the programmer uses the table like a list, adding items but never
	 * creating a column.
	 *
	 * @param index
	 *            the index of the column to return
	 * @return the column at the given index
	 * @exception IllegalArgumentException -
	 *                if the index is not between 0 and the number of elements
	 *                in the list minus 1 (inclusive)
	 *
	 * @since 3.3
	 */
	protected abstract Widget doGetColumn(int index);

	/**
	 * Returns the item at the given, zero-relative index in the receiver.
	 * Throws an exception if the index is out of range.
	 *
	 * @param index
	 *            the index of the item to return
	 * @return the item at the given index
	 * @exception IllegalArgumentException -
	 *                if the index is not between 0 and the number of elements
	 *                in the list minus 1 (inclusive)
	 *
	 * @since 3.3
	 */
	protected abstract Item doGetItem(int index);

	/**
	 * Returns an array of {@link Item} that are currently selected in the
	 * receiver. The order of the items is unspecified. An empty array indicates
	 * that no items are selected.
	 *
	 * @return an array representing the selection
	 *
	 * @since 3.3
	 */
	protected abstract Item[] doGetSelection();

	/**
	 * Returns the zero-relative indices of the items which are currently
	 * selected in the receiver. The order of the indices is unspecified. The
	 * array is empty if no items are selected.
	 *
	 * @return an array representing the selection
	 *
	 * @since 3.3
	 */
	protected abstract int[] doGetSelectionIndices();

	/**
	 * Clears all the items in the receiver. The text, icon and other attributes
	 * of the items are set to their default values. If the table was created
	 * with the <code>SWT.VIRTUAL</code> style, these attributes are requested
	 * again as needed.
	 *
	 * @since 3.3
	 */
	protected abstract void doClearAll();

	/**
	 * Resets the given item in the receiver. The text, icon and other attributes
	 * of the item are set to their default values.
	 *
	 * @param item the item to reset
	 *
	 * @since 3.3
	 */
	protected abstract void doResetItem(Item item);

	/**
	 * Removes the items from the receiver which are between the given
	 * zero-relative start and end indices (inclusive).
	 *
	 * @param start
	 *            the start of the range
	 * @param end
	 *            the end of the range
	 *
	 * @exception IllegalArgumentException -
	 *                if either the start or end are not between 0 and the
	 *                number of elements in the list minus 1 (inclusive)
	 *
	 * @since 3.3
	 */
	protected abstract void doRemove(int start, int end);

	/**
	 * Removes all of the items from the receiver.
	 *
	 * @since 3.3
	 */
	protected abstract void doRemoveAll();

	/**
	 * Removes the items from the receiver's list at the given zero-relative
	 * indices.
	 *
	 * @param indices
	 *            the array of indices of the items
	 *
	 * @exception IllegalArgumentException -
	 *                if the array is null, or if any of the indices is not
	 *                between 0 and the number of elements in the list minus 1
	 *                (inclusive)
	 *
	 * @since 3.3
	 */
	protected abstract void doRemove(int[] indices);

	/**
	 * Shows the item. If the item is already showing in the receiver, this
	 * method simply returns. Otherwise, the items are scrolled until the item
	 * is visible.
	 *
	 * @param item
	 *            the item to be shown
	 *
	 * @exception IllegalArgumentException -
	 *                if the item is null
	 *
	 * @since 3.3
	 */
	protected abstract void doShowItem(Item item);

	/**
	 * Deselects all selected items in the receiver.
	 *
	 * @since 3.3
	 */
	protected abstract void doDeselectAll();

	/**
	 * Sets the receiver's selection to be the given array of items. The current selection is
	 * cleared before the new items are selected, and if necessary the receiver is scrolled to make
	 * the new selection visible.
	 * <p>
	 * Items that are not in the receiver are ignored. If the receiver is single-select and multiple
	 * items are specified, then all items are ignored.
	 * </p>
	 *
	 * @param items the array of items
	 *
	 * @exception IllegalArgumentException - if the array of items is null
	 *
	 * @since 3.3
	 */
	protected abstract void doSetSelection(Item[] items);

	/**
	 * Shows the selection. If the selection is already showing in the receiver,
	 * this method simply returns. Otherwise, the items are scrolled until the
	 * selection is visible.
	 *
	 * @since 3.3
	 */
	protected abstract void doShowSelection();

	/**
	 * Selects the items at the given zero-relative indices in the receiver. The current selection
	 * is cleared before the new items are selected, and if necessary the receiver is scrolled to
	 * make the new selection visible.
	 * <p>
	 * Indices that are out of range and duplicate indices are ignored. If the receiver is
	 * single-select and multiple indices are specified, then all indices are ignored.
	 * </p>
	 *
	 * @param indices the indices of the items to select
	 *
	 * @exception IllegalArgumentException - if the array of indices is null
	 *
	 * @since 3.3
	 */
	protected abstract void doSetSelection(int[] indices);

	/**
	 * Clears the item at the given zero-relative index in the receiver. The
	 * text, icon and other attributes of the item are set to the default value.
	 * If the table was created with the <code>SWT.VIRTUAL</code> style, these
	 * attributes are requested again as needed.
	 *
	 * @param index
	 *            the index of the item to clear
	 *
	 * @exception IllegalArgumentException -
	 *                if the index is not between 0 and the number of elements
	 *                in the list minus 1 (inclusive)
	 *
	 * @see SWT#VIRTUAL
	 * @see SWT#SetData
	 *
	 * @since 3.3
	 */
	protected abstract void doClear(int index);



	/**
	 * Selects the items at the given zero-relative indices in the receiver.
	 * The current selection is not cleared before the new items are selected.
	 * <p>
	 * If the item at a given index is not selected, it is selected.
	 * If the item at a given index was already selected, it remains selected.
	 * Indices that are out of range and duplicate indices are ignored.
	 * If the receiver is single-select and multiple indices are specified,
	 * then all indices are ignored.
	 * </p>
	 *
	 * @param indices the array of indices for the items to select
	 *
	 * @exception IllegalArgumentException - if the array of indices is null
	 *
	 */
	protected abstract void doSelect(int[] indices);

}
