org.eclipse.jface.viewers.StructuredViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jface.viewers.StructuredViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Tom Schindl - bug 151205
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 402439, 475689
 *     Thorsten Maack <tm@tmaack.de> - Bug 482163
 *     Jan-Ove Weichel <janove.weichel@vogella.com> - Bug 481490
 *******************************************************************************/
package org.eclipse.jface.viewers;

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

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.internal.InternalPolicy;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableTreeItem;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

/**
 * Abstract base implementation for structure-oriented viewers (trees, lists,
 * tables). Supports custom sorting, filtering, and rendering.
 * <p>
 * Any number of viewer filters can be added to this viewer (using
 * <code>addFilter</code>). When the viewer receives an update, it asks each
 * of its filters if it is out of date, and refilters elements as required.
 * </p>
 *
 * @see ViewerFilter
 * @see ViewerComparator
 */
public abstract class StructuredViewer extends ContentViewer implements IPostSelectionProvider {

    /**
     * A map from the viewer's model elements to SWT widgets. (key type:
     * <code>Object</code>, value type: <code>Widget</code>, or <code>Widget[]</code>).
     * <code>null</code> means that the element map is disabled.
     */
    private CustomHashtable elementMap;

    /**
     * The comparer to use for comparing elements, or <code>null</code> to use
     * the default <code>equals</code> and <code>hashCode</code> methods on
     * the element itself.
     */
    private IElementComparer comparer;

    /**
     * This viewer's comparator used for sorting. <code>null</code> means there is no comparator.
     */
    private ViewerComparator sorter;

    /**
     * This viewer's filters (element type: <code>ViewerFilter</code>).
     * <code>null</code> means there are no filters.
     */
    private List filters;

    /**
     * Indicates whether the viewer should attempt to preserve the selection
     * across update operations.
     *
     * @see #setSelection(ISelection, boolean)
     */
    private boolean preserveSelection = true;

    /**
     * Indicates whether a selection change is in progress on this viewer.
     *
     * @see #setSelection(ISelection, boolean)
     */
    private boolean inChange;

    /**
     * Used while a selection change is in progress on this viewer to indicates
     * whether the selection should be restored.
     *
     * @see #setSelection(ISelection, boolean)
     */
    private boolean restoreSelection;

    /**
     * List of double-click state listeners (element type:
     * <code>IDoubleClickListener</code>).
     *
     * @see #fireDoubleClick
     */
    private ListenerList doubleClickListeners = new ListenerList();

    /**
     * List of open listeners (element type:
     * <code>ISelectionActivateListener</code>).
     *
     * @see #fireOpen
     */
    private ListenerList openListeners = new ListenerList();

    /**
     * List of post selection listeners (element type:
     * <code>ISelectionActivateListener</code>).
     *
     * @see #firePostSelectionChanged
     */
    private ListenerList postSelectionChangedListeners = new ListenerList();

    /**
     * The colorAndFontCollector is an object used by viewers that
     * support the IColorProvider, the IFontProvider and/or the
     * IViewerLabelProvider for color and font updates.
     * Initialize it to have no color or font providing
     * initially.
     * @since 3.1
     */
    private ColorAndFontCollector colorAndFontCollector = new ColorAndFontCollector();

    /**
     * Calls when associate() and disassociate() are called
     */
    private StructuredViewerInternals.AssociateListener associateListener;

    /**
     * Empty array of widgets.
     */
    private static Widget[] NO_WIDGETS = new Widget[0];

    /**
     * The ColorAndFontCollector is a helper class for viewers
     * that have color and font support ad optionally decorators.
     * @see IColorDecorator
     * @see IFontDecorator
     * @see IColorProvider
     * @see IFontProvider
     * @see IDecoration
     */
    protected class ColorAndFontCollectorWithProviders extends ColorAndFontCollector {

        IColorProvider colorProvider;

        IFontProvider fontProvider;

        /**
         * Create a new instance of the receiver using the supplied
         * label provider. If it is an IColorProvider or IFontProvider
         * set these values up.
         * @param provider IBaseLabelProvider
         * @see IColorProvider
         * @see IFontProvider
         */
        public ColorAndFontCollectorWithProviders(IBaseLabelProvider provider) {
            super();
            if (provider instanceof IColorProvider) {
                colorProvider = (IColorProvider) provider;
            }
            if (provider instanceof IFontProvider) {
                fontProvider = (IFontProvider) provider;
            }
        }

        @Override
        public void setFontsAndColors(Object element) {

            if (fontProvider != null) {
                if (font == null) {
                    font = fontProvider.getFont(element);
                }
            }

            if (colorProvider == null) {
                return;
            }
            //Set the colors if they are not set yet
            if (background == null) {
                background = colorProvider.getBackground(element);
            }

            if (foreground == null) {
                foreground = colorProvider.getForeground(element);
            }
        }

        @Override
        public void applyFontsAndColors(TableItem control) {

            if (colorProvider == null) {
                if (usedDecorators) {
                    //If there is no provider only apply set values
                    if (background != null) {
                        control.setBackground(background);
                    }

                    if (foreground != null) {
                        control.setForeground(foreground);
                    }
                }
            } else {
                //Always set the value if there is a provider
                control.setBackground(background);
                control.setForeground(foreground);
            }

            if (fontProvider == null) {
                if (usedDecorators && font != null) {
                    control.setFont(font);
                }
            } else {
                control.setFont(font);
            }

            clear();
        }

        @Override
        public void applyFontsAndColors(TreeItem control) {

            if (colorProvider == null) {
                if (usedDecorators) {
                    //If there is no provider only apply set values
                    if (background != null) {
                        control.setBackground(background);
                    }

                    if (foreground != null) {
                        control.setForeground(foreground);
                    }
                }
            } else {
                //Always set the value if there is a provider
                control.setBackground(background);
                control.setForeground(foreground);
            }

            if (fontProvider == null) {
                if (usedDecorators && font != null) {
                    control.setFont(font);
                }
            } else {
                control.setFont(font);
            }

            clear();
        }

        @Override
        public void applyFontsAndColors(TableTreeItem control) {

            if (colorProvider == null) {
                if (usedDecorators) {
                    //If there is no provider only apply set values
                    if (background != null) {
                        control.setBackground(background);
                    }

                    if (foreground != null) {
                        control.setForeground(foreground);
                    }
                }
            } else {
                //Always set the value if there is a provider
                control.setBackground(background);
                control.setForeground(foreground);
            }

            if (fontProvider == null) {
                if (usedDecorators && font != null) {
                    control.setFont(font);
                }
            } else {
                control.setFont(font);
            }

            clear();
        }

    }

    /**
     * The ColorAndFontCollector collects fonts and colors without a
     * a color or font provider.
     *
     */
    protected class ColorAndFontCollector {

        Color foreground = null;

        Color background = null;

        Font font = null;

        boolean usedDecorators = false;

        /**
         * Create a new instance of the receiver with
         * no color and font provider.
         */
        public ColorAndFontCollector() {
            super();
        }

        /**
         * Clear all of the results.
         */
        public void clear() {
            foreground = null;
            background = null;
            font = null;
            usedDecorators = false;
        }

        /**
         * Set the initial fonts and colors for the element from the
         * content providers.
         * @param element Object
         */
        public void setFontsAndColors(Object element) {
            //Do nothing if there are no providers
        }

        /**
         * Set that decorators were applied.
         */
        public void setUsedDecorators() {
            this.usedDecorators = true;
        }

        /**
         * Apply the fonts and colors to the control if
         * required.
         * @param control
         */
        public void applyFontsAndColors(TableItem control) {

            if (usedDecorators) {
                //If there is no provider only apply set values
                if (background != null) {
                    control.setBackground(background);
                }

                if (foreground != null) {
                    control.setForeground(foreground);
                }

                if (font != null) {
                    control.setFont(font);
                }
            }
            clear();
        }

        /**
         * Apply the fonts and colors to the control if
         * required.
         * @param control
         */
        public void applyFontsAndColors(TreeItem control) {
            if (usedDecorators) {
                //If there is no provider only apply set values
                if (background != null) {
                    control.setBackground(background);
                }

                if (foreground != null) {
                    control.setForeground(foreground);
                }

                if (font != null) {
                    control.setFont(font);
                }
            }
            clear();
        }

        /**
         * Apply the fonts and colors to the control if
         * required.
         * @param control
         */
        public void applyFontsAndColors(TableTreeItem control) {
            if (usedDecorators) {
                //If there is no provider only apply set values
                if (background != null) {
                    control.setBackground(background);
                }

                if (foreground != null) {
                    control.setForeground(foreground);
                }

                if (font != null) {
                    control.setFont(font);
                }
            }
            clear();
        }

        /**
         * Set the background color.
         * @param background
         */
        public void setBackground(Color background) {
            this.background = background;
        }

        /**
         * Set the font.
         * @param font
         */
        public void setFont(Font font) {
            this.font = font;
        }

        /**
         * Set the foreground color.
         * @param foreground
         */
        public void setForeground(Color foreground) {
            this.foreground = foreground;
        }

    }

    /**
     * The safe runnable used to update an item.
     */
    class UpdateItemSafeRunnable extends SafeRunnable {
        private Widget widget;

        private Object element;

        private boolean fullMap;

        UpdateItemSafeRunnable(Widget widget, Object element, boolean fullMap) {
            this.widget = widget;
            this.element = element;
            this.fullMap = fullMap;
        }

        @Override
        public void run() {
            doUpdateItem(widget, element, fullMap);
        }
    }

    /**
     * Creates a structured element viewer. The viewer has no input, no content
     * provider, a default label provider, no sorter, and no filters.
     */
    protected StructuredViewer() {
        // do nothing
    }

    /**
     * Adds a listener for double-clicks in this viewer. Has no effect if an
     * identical listener is already registered.
     *
     * @param listener
     *            a double-click listener
     */
    public void addDoubleClickListener(IDoubleClickListener listener) {
        doubleClickListeners.add(listener);
    }

    /**
     * Adds a listener for selection-open in this viewer. Has no effect if an
     * identical listener is already registered.
     *
     * @param listener
     *            an open listener
     */
    public void addOpenListener(IOpenListener listener) {
        openListeners.add(listener);
    }

    @Override
    public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
        postSelectionChangedListeners.add(listener);
    }

    /**
     * Adds support for dragging items out of this viewer via a user
     * drag-and-drop operation.
     *
     * @param operations
     *            a bitwise OR of the supported drag and drop operation types (
     *            <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
     *            <code>DROP_MOVE</code>)
     * @param transferTypes
     *            the transfer types that are supported by the drag operation
     * @param listener
     *            the callback that will be invoked to set the drag data and to
     *            cleanup after the drag and drop operation finishes
     * @see org.eclipse.swt.dnd.DND
     */
    public void addDragSupport(int operations, Transfer[] transferTypes, DragSourceListener listener) {

        Control myControl = getControl();
        final DragSource dragSource = new DragSource(myControl, operations);
        dragSource.setTransfer(transferTypes);
        dragSource.addDragListener(listener);
    }

    /**
     * Adds support for dropping items into this viewer via a user drag-and-drop
     * operation.
     *
     * @param operations
     *            a bitwise OR of the supported drag and drop operation types (
     *            <code>DROP_COPY</code>,<code>DROP_LINK</code>, and
     *            <code>DROP_MOVE</code>)
     * @param transferTypes
     *            the transfer types that are supported by the drop operation
     * @param listener
     *            the callback that will be invoked after the drag and drop
     *            operation finishes
     * @see org.eclipse.swt.dnd.DND
     */
    public void addDropSupport(int operations, Transfer[] transferTypes, final DropTargetListener listener) {
        Control control = getControl();
        DropTarget dropTarget = new DropTarget(control, operations);
        dropTarget.setTransfer(transferTypes);
        dropTarget.addDropListener(listener);
    }

    /**
     * Adds the given filter to this viewer, and triggers refiltering and
     * resorting of the elements. If you want to add more than one filter
     * consider using {@link StructuredViewer#setFilters(ViewerFilter...)}.
     *
     * @param filter
     *            a viewer filter
     * @see StructuredViewer#setFilters(ViewerFilter...)
     */
    public void addFilter(ViewerFilter filter) {
        if (filters == null) {
            filters = new ArrayList();
        }
        filters.add(filter);
        refresh();
    }

    /**
     * Asserts that the given array of elements is itself non- <code>null</code>
     * and contains no <code>null</code> elements.
     *
     * @param elements
     *            the array to check
     */
    protected void assertElementsNotNull(Object[] elements) {
        Assert.isNotNull(elements);
        for (int i = 0, n = elements.length; i < n; ++i) {
            Assert.isNotNull(elements[i]);
        }

        if (InternalPolicy.DEBUG_LOG_EQUAL_VIEWER_ELEMENTS && elements.length > 1) {
            CustomHashtable elementSet = newHashtable(elements.length * 2);
            for (int i = 0; i < elements.length; i++) {
                Object element = elements[i];
                Object old = elementSet.put(element, element);
                if (old != null) {
                    String message = "Sibling elements in viewer must not be equal:\n  " //$NON-NLS-1$
                            + old + ",\n  " + element; //$NON-NLS-1$
                    Policy.getLog().log(new Status(IStatus.WARNING, Policy.JFACE, message, new RuntimeException()));
                    return;
                }
            }
        }
    }

    /**
     * Associates the given element with the given widget. Sets the given item's
     * data to be the element, and maps the element to the item in the element
     * map (if enabled).
     *
     * @param element
     *            the element
     * @param item
     *            the widget
     */
    protected void associate(Object element, Item item) {
        Object data = item.getData();
        if (data != element) {
            if (data != null) {
                disassociate(item);
            }
            item.setData(element);
            mapElement(element, item);
        } else {
            // Always map the element, even if data == element,
            // since unmapAllElements() can leave the map inconsistent
            // See bug 2741 for details.
            mapElement(element, item);
        }
        if (associateListener != null)
            associateListener.associate(element, item);
    }

    /**
     * Disassociates the given SWT item from its corresponding element. Sets the
     * item's data to <code>null</code> and removes the element from the
     * element map (if enabled).
     *
     * @param item
     *            the widget
     */
    protected void disassociate(Item item) {
        if (associateListener != null)
            associateListener.disassociate(item);
        Object element = item.getData();
        Assert.isNotNull(element);
        //Clear the map before we clear the data
        unmapElement(element, item);
        item.setData(null);
    }

    /**
     * Returns the widget in this viewer's control which represents the given
     * element if it is the viewer's input.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param element
     * @return the corresponding widget, or <code>null</code> if none
     */
    protected abstract Widget doFindInputItem(Object element);

    /**
     * Returns the widget in this viewer's control which represent the given
     * element. This method searches all the children of the input element.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param element
     * @return the corresponding widget, or <code>null</code> if none
     */
    protected abstract Widget doFindItem(Object element);

    /**
     * Copies the attributes of the given element into the given SWT item. The
     * element map is updated according to the value of <code>fullMap</code>.
     * If <code>fullMap</code> is <code>true</code> then the current mapping
     * from element to widgets is removed and the new mapping is added. If
     * full map is <code>false</code> then only the new map gets installed.
     * Installing only the new map is necessary in cases where only the order of
     * elements changes but not the set of elements.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param item
     * @param element element
     * @param fullMap
     *            <code>true</code> if mappings are added and removed, and
     *            <code>false</code> if only the new map gets installed
     */
    protected abstract void doUpdateItem(Widget item, Object element, boolean fullMap);

    /**
     * Compares two elements for equality. Uses the element comparer if one has
     * been set, otherwise uses the default <code>equals</code> method on the
     * elements themselves.
     *
     * @param elementA
     *            the first element
     * @param elementB
     *            the second element
     * @return whether elementA is equal to elementB
     */
    protected boolean equals(Object elementA, Object elementB) {
        if (comparer == null) {
            return elementA == null ? elementB == null : elementA.equals(elementB);
        } else {
            return elementA == null ? elementB == null : comparer.equals(elementA, elementB);
        }
    }

    /**
     * Returns the result of running the given elements through the filters.
     *
     * @param elements
     *            the elements to filter
     * @return only the elements which all filters accept
     */
    protected Object[] filter(Object[] elements) {
        if (filters != null) {
            ArrayList filtered = new ArrayList(elements.length);
            Object root = getRoot();
            for (int i = 0; i < elements.length; i++) {
                boolean add = true;
                for (int j = 0; j < filters.size(); j++) {
                    add = ((ViewerFilter) filters.get(j)).select(this, root, elements[i]);
                    if (!add) {
                        break;
                    }
                }
                if (add) {
                    filtered.add(elements[i]);
                } else {
                    if (associateListener != null)
                        associateListener.filteredOut(elements[i]);
                }
            }
            return filtered.toArray();
        }
        return elements;
    }

    /**
     * Finds the widget which represents the given element.
     * <p>
     * The default implementation of this method tries first to find the widget
     * for the given element assuming that it is the viewer's input; this is
     * done by calling <code>doFindInputItem</code>. If it is not found
     * there, it is looked up in the internal element map provided that this
     * feature has been enabled. If the element map is disabled, the widget is
     * found via <code>doFindInputItem</code>.
     * </p>
     *
     * @param element
     *            the element
     * @return the corresponding widget, or <code>null</code> if none
     */
    protected final Widget findItem(Object element) {
        Widget[] result = findItems(element);
        return result.length == 0 ? null : result[0];
    }

    /**
     * Finds the widgets which represent the given element. The returned array
     * must not be changed by clients; it might change upon calling other
     * methods on this viewer.
     * <p>
     * This method was introduced to support multiple equal elements in a viewer
     * (@see {@link AbstractTreeViewer}). Multiple equal elements are only
     * supported if the element map is enabled by calling
     * {@link #setUseHashlookup(boolean)} and passing <code>true</code>.
     * </p>
     * <p>
     * The default implementation of this method tries first to find the widget
     * for the given element assuming that it is the viewer's input; this is
     * done by calling <code>doFindInputItem</code>. If it is not found
     * there, the widgets are looked up in the internal element map provided
     * that this feature has been enabled. If the element map is disabled, the
     * widget is found via <code>doFindItem</code>.
     * </p>
     *
     * @param element
     *            the element
     * @return the corresponding widgets
     *
     * @since 3.2
     */
    protected final Widget[] findItems(Object element) {
        Widget result = doFindInputItem(element);
        if (result != null) {
            return new Widget[] { result };
        }
        // if we have an element map use it, otherwise search for the item.
        if (usingElementMap()) {
            Object widgetOrWidgets = elementMap.get(element);
            if (widgetOrWidgets == null) {
                return NO_WIDGETS;
            } else if (widgetOrWidgets instanceof Widget) {
                return new Widget[] { (Widget) widgetOrWidgets };
            } else {
                return (Widget[]) widgetOrWidgets;
            }
        }
        result = doFindItem(element);
        return result == null ? NO_WIDGETS : new Widget[] { result };
    }

    /**
     * Notifies any double-click listeners that a double-click has been
     * received. Only listeners registered at the time this method is called are
     * notified.
     *
     * @param event
     *            a double-click event
     *
     * @see IDoubleClickListener#doubleClick
     */
    protected void fireDoubleClick(final DoubleClickEvent event) {
        Object[] listeners = doubleClickListeners.getListeners();
        for (int i = 0; i < listeners.length; ++i) {
            final IDoubleClickListener l = (IDoubleClickListener) listeners[i];
            SafeRunnable.run(new SafeRunnable() {
                @Override
                public void run() {
                    l.doubleClick(event);
                }
            });
        }
    }

    /**
     * Notifies any open event listeners that a open event has been received.
     * Only listeners registered at the time this method is called are notified.
     *
     * @param event
     *            a double-click event
     *
     * @see IOpenListener#open(OpenEvent)
     */
    protected void fireOpen(final OpenEvent event) {
        Object[] listeners = openListeners.getListeners();
        for (int i = 0; i < listeners.length; ++i) {
            final IOpenListener l = (IOpenListener) listeners[i];
            SafeRunnable.run(new SafeRunnable() {
                @Override
                public void run() {
                    l.open(event);
                }
            });
        }
    }

    /**
     * Notifies any post selection listeners that a post selection event has
     * been received. Only listeners registered at the time this method is
     * called are notified.
     *
     * @param event
     *            a selection changed event
     *
     * @see #addPostSelectionChangedListener(ISelectionChangedListener)
     */
    protected void firePostSelectionChanged(final SelectionChangedEvent event) {
        Object[] listeners = postSelectionChangedListeners.getListeners();
        for (int i = 0; i < listeners.length; ++i) {
            final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
            SafeRunnable.run(new SafeRunnable() {
                @Override
                public void run() {
                    l.selectionChanged(event);
                }
            });
        }
    }

    /**
     * Returns the comparer to use for comparing elements, or
     * <code>null</code> if none has been set.  If specified,
     * the viewer uses this to compare and hash elements rather
     * than the elements' own equals and hashCode methods.
     *
     * @return the comparer to use for comparing elements or
     *            <code>null</code>
     */
    public IElementComparer getComparer() {
        return comparer;
    }

    /**
     * Returns the filtered array of children of the given element. The
     * resulting array must not be modified, as it may come directly from the
     * model's internal state.
     *
     * @param parent
     *            the parent element
     * @return a filtered array of child elements
     */
    protected Object[] getFilteredChildren(Object parent) {
        Object[] result = getRawChildren(parent);
        if (filters != null) {
            for (Iterator iter = filters.iterator(); iter.hasNext();) {
                ViewerFilter f = (ViewerFilter) iter.next();
                Object[] filteredResult = f.filter(this, parent, result);
                if (associateListener != null && filteredResult.length != result.length) {
                    notifyFilteredOut(result, filteredResult);
                }
                result = filteredResult;
            }
        }
        return result;
    }

    /**
     * Notifies an AssociateListener of the elements that have been filtered out.
     *
     * @param rawResult
     * @param filteredResult
     */
    private void notifyFilteredOut(Object[] rawResult, Object[] filteredResult) {
        int rawIndex = 0;
        int filteredIndex = 0;
        for (; filteredIndex < filteredResult.length;) {
            if (rawResult[rawIndex] != filteredResult[filteredIndex]) {
                associateListener.filteredOut(rawResult[rawIndex++]);
                continue;
            }
            rawIndex++;
            filteredIndex++;
        }
        for (; rawIndex < rawResult.length; rawIndex++) {
            associateListener.filteredOut(rawResult[rawIndex]);
        }
    }

    /**
     * Returns this viewer's filters.
     *
     * @return an array of viewer filters
     * @see StructuredViewer#setFilters(ViewerFilter...)
     */
    public ViewerFilter[] getFilters() {
        if (filters == null) {
            return new ViewerFilter[0];
        }
        ViewerFilter[] result = new ViewerFilter[filters.size()];
        filters.toArray(result);
        return result;
    }

    /**
     * Returns the item at the given display-relative coordinates, or
     * <code>null</code> if there is no item at that location or
     * the underlying SWT-Control is not made up of {@link Item}
     * (e.g {@link ListViewer})
     * <p>
     * The default implementation of this method returns <code>null</code>.
     * </p>
     *
     * @param x
     *            horizontal coordinate
     * @param y
     *            vertical coordinate
     * @return the item, or <code>null</code> if there is no item at the given
     *         coordinates
     * @deprecated This method is deprecated in 3.3 in favor of {@link ColumnViewer#getItemAt(org.eclipse.swt.graphics.Point)}.
     * Viewers who are not subclasses of {@link ColumnViewer} should consider using a
     * widget relative implementation like {@link ColumnViewer#getItemAt(org.eclipse.swt.graphics.Point)}.
     *
     */
    @Deprecated
    protected Item getItem(int x, int y) {
        return null;
    }

    /**
     * Returns the children of the given parent without sorting and filtering
     * them. The resulting array must not be modified, as it may come directly
     * from the model's internal state.
     * <p>
     * Returns an empty array if the given parent is <code>null</code>.
     * </p>
     *
     * @param parent
     *            the parent element
     * @return the child elements
     */
    protected Object[] getRawChildren(Object parent) {
        Object[] result = null;
        if (parent != null) {
            IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider();
            if (cp != null) {
                result = cp.getElements(parent);
                assertElementsNotNull(result);
            }
        }
        return (result != null) ? result : new Object[0];
    }

    /**
     * Returns the root element.
     * <p>
     * The default implementation of this framework method forwards to
     * <code>getInput</code>. Override if the root element is different from
     * the viewer's input element.
     * </p>
     *
     * @return the root element, or <code>null</code> if none
     */
    protected Object getRoot() {
        return getInput();
    }

    /**
     * The <code>StructuredViewer</code> implementation of this method returns
     * the result as an <code>IStructuredSelection</code>.
     * <p>
     * Call {@link #getStructuredSelection()} instead to get an instance of
     * <code>IStructuredSelection</code> directly.
     * </p>
     * Subclasses do not typically override this method, but implement
     * <code>getSelectionFromWidget(List)</code> instead. If they override this
     * method, they should return an <code>IStructuredSelection</code> as well.
     *
     * @return ISelection
     */
    @Override
    public ISelection getSelection() {
        Control control = getControl();
        if (control == null || control.isDisposed()) {
            return StructuredSelection.EMPTY;
        }
        List list = getSelectionFromWidget();
        return new StructuredSelection(list, comparer);
    }

    /**
     * Returns the <code>IStructuredSelection</code> of this viewer.
     * <p>
     * Subclasses whose {@link #getSelection()} specifies to return a more
     * specific type should also override this method and return that type.
     * </p>
     *
     * @return IStructuredSelection
     * @throws ClassCastException
     *             if the selection of the viewer is not an instance of
     *             IStructuredSelection
     * @since 3.11
     */
    public IStructuredSelection getStructuredSelection() throws ClassCastException {
        ISelection selection = getSelection();
        if (selection instanceof IStructuredSelection) {
            return (IStructuredSelection) selection;
        }
        throw new ClassCastException(
                "StructuredViewer should return an instance of IStructuredSelection from its getSelection() method."); //$NON-NLS-1$
    }

    /**
     * Retrieves the selection, as a <code>List</code>, from the underlying
     * widget.
     *
     * @return the list of selected elements
     */
    protected abstract List getSelectionFromWidget();

    /**
     * Returns the sorted and filtered set of children of the given element. The
     * resulting array must not be modified, as it may come directly from the
     * model's internal state.
     *
     * @param parent
     *            the parent element
     * @return a sorted and filtered array of child elements
     */
    protected Object[] getSortedChildren(Object parent) {
        Object[] result = getFilteredChildren(parent);
        if (sorter != null) {
            // be sure we're not modifying the original array from the model
            result = result.clone();
            sorter.sort(this, result);
        }
        return result;
    }

    /**
     * Returns this viewer's sorter, or <code>null</code> if it does not have
     * one.  If this viewer has a comparator that was set via
     * <code>setComparator(ViewerComparator)</code> then this method will return
     * <code>null</code> if the comparator is not an instance of ViewerSorter.
      * <p>
      * It is recommended to use <code>getComparator()</code> instead.
      * </p>
     *
     * @return a viewer sorter, or <code>null</code> if none or if the comparator is
     *             not an instance of ViewerSorter
     */
    public ViewerSorter getSorter() {
        if (sorter instanceof ViewerSorter)
            return (ViewerSorter) sorter;
        return null;
    }

    /**
     * Return this viewer's comparator used to sort elements.
     * This method should be used instead of <code>getSorter()</code>.
     *
     * @return a viewer comparator, or <code>null</code> if none
      *
     * @since 3.2
     */
    public ViewerComparator getComparator() {
        return sorter;
    }

    /**
     * Handles a double-click select event from the widget.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param event
     *            the SWT selection event
     */
    protected void handleDoubleSelect(SelectionEvent event) {
        // This method is reimplemented in AbstractTreeViewer to fix bug 108102.

        // handle case where an earlier selection listener disposed the control.
        Control control = getControl();
        if (control != null && !control.isDisposed()) {
            // If the double-clicked element can be obtained from the event, use it
            // otherwise get it from the control.  Some controls like List do
            // not have the notion of item.
            // For details, see bug 90161 [Navigator] DefaultSelecting folders shouldn't always expand first one
            ISelection selection;
            if (event.item != null && event.item.getData() != null) {
                selection = new StructuredSelection(event.item.getData());
            } else {
                selection = getSelection();
                updateSelection(selection);
            }
            fireDoubleClick(new DoubleClickEvent(this, selection));
        }
    }

    /**
     * Handles an open event from the OpenStrategy.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param event
     *            the SWT selection event
     */
    protected void handleOpen(SelectionEvent event) {
        Control control = getControl();
        if (control != null && !control.isDisposed()) {
            ISelection selection = getSelection();
            fireOpen(new OpenEvent(this, selection));
        }
    }

    /**
     * Handles an invalid selection.
     * <p>
     * This framework method is called if a model change picked up by a viewer
     * results in an invalid selection. For instance if an element contained in
     * the selection has been removed from the viewer, the viewer is free to
     * either remove the element from the selection or to pick another element
     * as its new selection. The default implementation of this method calls
     * <code>updateSelection</code>. Subclasses may override it to implement
     * a different strategy for picking a new selection when the old selection
     * becomes invalid.
     * </p>
     *
     * @param invalidSelection
     *            the selection before the viewer was updated
     * @param newSelection
     *            the selection after the update, or <code>null</code> if none
     */
    protected void handleInvalidSelection(ISelection invalidSelection, ISelection newSelection) {
        updateSelection(newSelection);
        SelectionChangedEvent event = new SelectionChangedEvent(this, newSelection);
        firePostSelectionChanged(event);
    }

    /**
     * The <code>StructuredViewer</code> implementation of this
     * <code>ContentViewer</code> method calls <code>update</code> if the
     * event specifies that the label of a given element has changed, otherwise
     * it calls super. Subclasses may reimplement or extend.
     * </p>
     * @param event the event that generated this update
     */
    @Override
    protected void handleLabelProviderChanged(LabelProviderChangedEvent event) {
        Object[] elements = event.getElements();
        if (elements != null) {
            update(elements, null);
        } else {
            super.handleLabelProviderChanged(event);
        }
    }

    /**
     * Handles a select event from the widget.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param event
     *            the SWT selection event
     */
    protected void handleSelect(SelectionEvent event) {
        // handle case where an earlier selection listener disposed the control.
        Control control = getControl();
        if (control != null && !control.isDisposed()) {
            updateSelection(getSelection());
        }
    }

    /**
     * Handles a post select event from the widget.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param e the SWT selection event
     */
    protected void handlePostSelect(SelectionEvent e) {
        SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
        firePostSelectionChanged(event);
    }

    @Override
    protected void hookControl(Control control) {
        super.hookControl(control);
        OpenStrategy handler = new OpenStrategy(control);
        handler.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                // On Windows, selection events may happen during a refresh.
                // Ignore these events if we are currently in preservingSelection().
                // See bug 184441.
                if (!inChange) {
                    handleSelect(e);
                }
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                handleDoubleSelect(e);
            }
        });
        handler.addPostSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                handlePostSelect(e);
            }
        });
        handler.addOpenListener(StructuredViewer.this::handleOpen);
    }

    /**
     * Returns whether this viewer has any filters.
     * @return boolean
     */
    protected boolean hasFilters() {
        return filters != null && filters.size() > 0;
    }

    /**
     * Refreshes this viewer starting at the given element.
     *
     * @param element
     *            the element
     */
    protected abstract void internalRefresh(Object element);

    /**
     * Refreshes this viewer starting at the given element. Labels are updated
     * as described in <code>refresh(boolean updateLabels)</code>.
     * <p>
     * The default implementation simply calls
     * <code>internalRefresh(element)</code>, ignoring
     * <code>updateLabels</code>.
     * <p>
     * If this method is overridden to do the actual refresh, then
     * <code>internalRefresh(Object element)</code> should simply call
     * <code>internalRefresh(element, true)</code>.
     *
     * @param element
     *            the element
     * @param updateLabels
     *            <code>true</code> to update labels for existing elements,
     *            <code>false</code> to only update labels as needed, assuming
     *            that labels for existing elements are unchanged.
     *
     * @since 2.0
     */
    protected void internalRefresh(Object element, boolean updateLabels) {
        internalRefresh(element);
    }

    /**
     * Adds the element item pair to the element map.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param element
     *            the element
     * @param item
     *            the corresponding widget
     */
    protected void mapElement(Object element, Widget item) {
        if (elementMap != null) {
            Object widgetOrWidgets = elementMap.get(element);
            if (widgetOrWidgets == null) {
                elementMap.put(element, item);
            } else if (widgetOrWidgets instanceof Widget) {
                if (widgetOrWidgets != item) {
                    elementMap.put(element, new Widget[] { (Widget) widgetOrWidgets, item });
                }
            } else {
                Widget[] widgets = (Widget[]) widgetOrWidgets;
                int indexOfItem = Arrays.asList(widgets).indexOf(item);
                if (indexOfItem == -1) {
                    int length = widgets.length;
                    System.arraycopy(widgets, 0, widgets = new Widget[length + 1], 0, length);
                    widgets[length] = item;
                    elementMap.put(element, widgets);
                }
            }
        }
    }

    /**
     * Determines whether a change to the given property of the given element
     * would require refiltering and/or resorting.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param element
     *            the element
     * @param property
     *            the property
     * @return <code>true</code> if refiltering is required, and
     *         <code>false</code> otherwise
     */
    protected boolean needsRefilter(Object element, String property) {
        if (sorter != null && sorter.isSorterProperty(element, property)) {
            return true;
        }

        if (filters != null) {
            for (int i = 0, n = filters.size(); i < n; ++i) {
                ViewerFilter filter = (ViewerFilter) filters.get(i);
                if (filter.isFilterProperty(element, property)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns a new hashtable using the given capacity and this viewer's element comparer.
     *
     * @param capacity the initial capacity of the hashtable
     * @return a new hashtable
     *
     * @since 3.0
     */
    CustomHashtable newHashtable(int capacity) {
        return new CustomHashtable(capacity, getComparer());
    }

    /**
     * Attempts to preserves the current selection across a run of the given
     * code. This method should not preserve the selection if
     * {link #getPreserveSelection()} returns false.
     * <p>
     * The default implementation of this method:
     * <ul>
     * <li>discovers the old selection (via <code>getSelection</code>)</li>
     * <li>runs the given runnable</li>
     * <li>attempts to restore the old selection (using
     * <code>setSelectionToWidget</code></li>
     * <li>rediscovers the resulting selection (via <code>getSelection</code>)</li>
     * <li>calls <code>handleInvalidSelection</code> if the resulting selection
     * is different from the old selection</li>
     * </ul>
     * </p>
     *
     * @param updateCode
     *            the code to run
     *
     * see #getPreserveSelection()
     */
    protected void preservingSelection(Runnable updateCode) {
        preservingSelection(updateCode, false);
    }

    /**
     * Attempts to preserves the current selection across a run of the given
     * code, with a best effort to avoid scrolling if <code>reveal</code> is
     * false, or to reveal the selection if <code>reveal</code> is true.
     * <p>
     * The default implementation of this method:
     * <ul>
     * <li>discovers the old selection (via <code>getSelection</code>)</li>
     * <li>runs the given runnable</li>
     * <li>attempts to restore the old selection (using
     * <code>setSelectionToWidget</code></li>
     * <li>rediscovers the resulting selection (via <code>getSelection</code>)</li>
     * <li>calls <code>handleInvalidSelection</code> if the selection did not
     * take</li>
     * </ul>
     * </p>
     *
     * @param updateCode
     *            the code to run
     * @param reveal
     *            <code>true</code> if the selection should be made visible,
     *            <code>false</code> if scrolling should be avoided
     * @since 3.3
     */
    void preservingSelection(Runnable updateCode, boolean reveal) {
        if (!preserveSelection) {
            return;
        }

        ISelection oldSelection = null;
        try {
            // preserve selection
            oldSelection = getSelection();
            inChange = restoreSelection = true;

            // perform the update
            updateCode.run();

        } finally {
            inChange = false;

            // restore selection
            if (restoreSelection) {
                setSelectionToWidget(oldSelection, reveal);
            }

            // send out notification if old and new differ
            ISelection newSelection = getSelection();
            if (!newSelection.equals(oldSelection)) {
                handleInvalidSelection(oldSelection, newSelection);
            }
        }
    }

    @Override
    public void refresh() {
        refresh(getRoot());
    }

    /**
     * Refreshes this viewer with information freshly obtained from this
     * viewer's model. If <code>updateLabels</code> is <code>true</code>
     * then labels for otherwise unaffected elements are updated as well.
     * Otherwise, it assumes labels for existing elements are unchanged, and
     * labels are only obtained as needed (for example, for new elements).
     * <p>
     * Calling <code>refresh(true)</code> has the same effect as
     * <code>refresh()</code>.
     * <p>
     * Note that the implementation may still obtain labels for existing
     * elements even if <code>updateLabels</code> is false. The intent is
     * simply to allow optimization where possible.
     *
     * @param updateLabels
     *            <code>true</code> to update labels for existing elements,
     *            <code>false</code> to only update labels as needed, assuming
     *            that labels for existing elements are unchanged.
     *
     * @since 2.0
     */
    public void refresh(boolean updateLabels) {
        refresh(getRoot(), updateLabels);
    }

    /**
     * Refreshes this viewer starting with the given element.
     * <p>
     * Unlike the <code>update</code> methods, this handles structural changes
     * to the given element (e.g. addition or removal of children). If only the
     * given element needs updating, it is more efficient to use the
     * <code>update</code> methods.
     * </p>
     *
     * @param element
     *            the element
     */
    public void refresh(final Object element) {
        preservingSelection(() -> internalRefresh(element));
    }

    /**
     * Refreshes this viewer starting with the given element. Labels are updated
     * as described in <code>refresh(boolean updateLabels)</code>.
     * <p>
     * Unlike the <code>update</code> methods, this handles structural changes
     * to the given element (e.g. addition or removal of children). If only the
     * given element needs updating, it is more efficient to use the
     * <code>update</code> methods.
     * </p>
     *
     * @param element
     *            the element
     * @param updateLabels
     *            <code>true</code> to update labels for existing elements,
     *            <code>false</code> to only update labels as needed, assuming
     *            that labels for existing elements are unchanged.
     *
     * @since 2.0
     */
    public void refresh(final Object element, final boolean updateLabels) {
        preservingSelection(() -> internalRefresh(element, updateLabels));
    }

    /**
     *
     * Refreshes the given item with the given element. Calls
     * <code>doUpdateItem(..., false)</code>.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     * @param widget
      *            the widget
     * @param element
      *            the element
     */
    protected final void refreshItem(Widget widget, Object element) {
        SafeRunnable.run(new UpdateItemSafeRunnable(widget, element, true));
    }

    /**
     * Removes the given open listener from this viewer. Has no effect if an
     * identical listener is not registered.
     *
     * @param listener
     *            an open listener
     */
    public void removeOpenListener(IOpenListener listener) {
        openListeners.remove(listener);
    }

    @Override
    public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
        postSelectionChangedListeners.remove(listener);
    }

    /**
     * Removes the given double-click listener from this viewer. Has no effect
     * if an identical listener is not registered.
     *
     * @param listener
     *            a double-click listener
     */
    public void removeDoubleClickListener(IDoubleClickListener listener) {
        doubleClickListeners.remove(listener);
    }

    /**
     * Removes the given filter from this viewer, and triggers refiltering and
     * resorting of the elements if required. Has no effect if the identical
     * filter is not registered. If you want to remove more than one filter
     * consider using {@link StructuredViewer#setFilters(ViewerFilter...)}.
     *
     * @param filter
     *            a viewer filter
     * @see StructuredViewer#setFilters(ViewerFilter...)
     */
    public void removeFilter(ViewerFilter filter) {
        Assert.isNotNull(filter);
        if (filters != null) {
            // Note: can't use List.remove(Object). Use identity comparison
            // instead.
            for (Iterator i = filters.iterator(); i.hasNext();) {
                Object o = i.next();
                if (o == filter) {
                    i.remove();
                    refresh();
                    if (filters.size() == 0) {
                        filters = null;
                    }
                    return;
                }
            }
        }
    }

    void setAssociateListener(StructuredViewerInternals.AssociateListener l) {
        associateListener = l;
    }

    /**
     * Sets the filters, replacing any previous filters, and triggers
     * refiltering and resorting of the elements.
     *
     * @param filters
     *            an varargs of viewer filters
     * @since 3.3
     */
    public void setFilters(ViewerFilter... filters) {
        if (filters.length == 0) {
            resetFilters();
        } else {
            this.filters = new ArrayList(Arrays.asList(filters));
            refresh();
        }
    }

    /**
     * Discards this viewer's filters and triggers refiltering and resorting of
     * the elements.
     */
    public void resetFilters() {
        if (filters != null) {
            filters = null;
            refresh();
        }
    }

    /**
     * Ensures that the given element is visible, scrolling the viewer if
     * necessary. The selection is unchanged.
     *
     * @param element
     *            the element to reveal
     */
    public abstract void reveal(Object element);

    /**
     * {@inheritDoc}
     * <p>
     * The <code>StructuredViewer</code> implementation of this method calls
     * {@link #assertContentProviderType(IContentProvider)} to validate the
     * content provider. For a <code>StructuredViewer</code>, the content
     * provider must implement {@link IStructuredContentProvider}.
     * </p>
     */
    @Override
    public void setContentProvider(IContentProvider provider) {
        assertContentProviderType(provider);
        super.setContentProvider(provider);
    }

    /**
     * Assert that the content provider is of one of the
     * supported types.
     * @param provider
     */
    protected void assertContentProviderType(IContentProvider provider) {
        Assert.isTrue(provider instanceof IStructuredContentProvider);
    }

    @Override
    public final void setInput(Object input) {
        Control control = getControl();
        if (control == null || control.isDisposed()) {
            throw new IllegalStateException("Need an underlying widget to be able to set the input." + //$NON-NLS-1$
                    "(Has the widget been disposed?)"); //$NON-NLS-1$
        }
        try {
            //      fInChange= true;

            unmapAllElements();

            super.setInput(input);

        } finally {
            //      fInChange= false;
        }
    }

    @Override
    public void setSelection(ISelection selection, boolean reveal) {
        /**
         * <p>
         * If the new selection differs from the current selection the hook
         * <code>updateSelection</code> is called.
         * </p>
         * <p>
         * If <code>setSelection</code> is called from within
         * <code>preserveSelection</code>, the call to
         * <code>updateSelection</code> is delayed until the end of
         * <code>preserveSelection</code>.
         * </p>
         * <p>
         * Subclasses do not typically override this method, but implement
         * <code>setSelectionToWidget</code> instead.
         * </p>
         */
        Control control = getControl();
        if (control == null || control.isDisposed()) {
            return;
        }
        if (!inChange) {
            setSelectionToWidget(selection, reveal);
            ISelection sel = getSelection();
            updateSelection(sel);
            firePostSelectionChanged(new SelectionChangedEvent(this, sel));
        } else {
            restoreSelection = false;
            setSelectionToWidget(selection, reveal);
        }
    }

    /**
     * Parlays the given list of selected elements into selections on this
     * viewer's control.
     * <p>
     * Subclasses should override to set their selection based on the given list
     * of elements.
     * </p>
     *
     * @param l
     *            list of selected elements (element type: <code>Object</code>)
     *            or <code>null</code> if the selection is to be cleared
     * @param reveal
     *            <code>true</code> if the selection is to be made visible,
     *            and <code>false</code> otherwise
     */
    protected abstract void setSelectionToWidget(List l, boolean reveal);

    /**
     * Converts the selection to a <code>List</code> and calls
     * <code>setSelectionToWidget(List, boolean)</code>. The selection is
     * expected to be an <code>IStructuredSelection</code> of elements. If
     * not, the selection is cleared.
     * <p>
     * Subclasses do not typically override this method, but implement
     * <code>setSelectionToWidget(List, boolean)</code> instead.
     *
     * @param selection
     *            an IStructuredSelection of elements
     * @param reveal
     *            <code>true</code> to reveal the first element in the
     *            selection, or <code>false</code> otherwise
     */
    protected void setSelectionToWidget(ISelection selection, boolean reveal) {
        if (selection instanceof IStructuredSelection) {
            setSelectionToWidget(((IStructuredSelection) selection).toList(), reveal);
        } else {
            setSelectionToWidget((List) null, reveal);
        }
    }

    /**
     * Sets this viewer's sorter and triggers refiltering and resorting of this
     * viewer's element. Passing <code>null</code> turns sorting off.
     * <p>
     *
     * @deprecated use <code>setComparator()</code> instead.
     *             </p>
     *
     * @param sorter
     *            a viewer sorter, or <code>null</code> if none
     */
    @Deprecated
    public void setSorter(ViewerSorter sorter) {
        if (this.sorter != sorter) {
            this.sorter = sorter;
            refresh();
        }
    }

    /**
     * Sets this viewer's comparator to be used for sorting elements, and triggers refiltering and
     * resorting of this viewer's element.  <code>null</code> turns sorting off.
     * To get the viewer's comparator, call <code>getComparator()</code>.
      * <p>
      * IMPORTANT: This method was introduced in 3.2. If a reference to this viewer object
      * is passed to clients who call <code>getSorter()</code>, null may be returned from
      * from that method even though the viewer is sorting its elements using the
      * viewer's comparator.
      * </p>
     *
     * @param comparator a viewer comparator, or <code>null</code> if none
      *
      * @since 3.2
     */
    public void setComparator(ViewerComparator comparator) {
        if (this.sorter != comparator) {
            this.sorter = comparator;
            refresh();
        }
    }

    /**
     * Configures whether this structured viewer uses an internal hash table to
     * speeds up the mapping between elements and SWT items. This must be called
     * before the viewer is given an input (via <code>setInput</code>).
     *
     * @param enable
     *            <code>true</code> to enable hash lookup, and
     *            <code>false</code> to disable it
     */
    public void setUseHashlookup(boolean enable) {
        Assert.isTrue(getInput() == null, "Can only enable the hash look up before input has been set");//$NON-NLS-1$
        if (enable) {
            elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY);
        } else {
            elementMap = null;
        }
    }

    /**
     * Sets the comparer to use for comparing elements, or <code>null</code>
     * to use the default <code>equals</code> and <code>hashCode</code>
     * methods on the elements themselves.
     *
     * @param comparer
     *            the comparer to use for comparing elements or
     *            <code>null</code>
     */
    public void setComparer(IElementComparer comparer) {
        this.comparer = comparer;
        if (elementMap != null) {
            elementMap = new CustomHashtable(elementMap, comparer);
        }
    }

    /**
     * NON-API - to be removed - see bug 200214
     * Enable or disable the preserve selection behavior of this viewer. The
     * default is that the viewer attempts to preserve the selection across
     * update operations. This is an advanced option, to support clients that
     * manage the selection without relying on the viewer, or clients running
     * into performance problems when using viewers and {@link SWT#VIRTUAL}.
     * Note that this method has been introduced in 3.5 and that trying to
     * disable the selection behavior may not be possible for all subclasses of
     * <code>StructuredViewer</code>, or may cause program errors. This method
     * is supported for {@link TableViewer}, {@link TreeViewer},
     * {@link ListViewer}, {@link CheckboxTableViewer},
     * {@link CheckboxTreeViewer}, and {@link ComboViewer}, but no promises are
     * made for other subclasses of StructuredViewer, or subclasses of the
     * listed viewer classes.
     *
     * @param preserve
     *            <code>true</code> if selection should be preserved,
     *            <code>false</code> otherwise
     */
    void setPreserveSelection(boolean preserve) {
        this.preserveSelection = preserve;
    }

    /**
     * NON-API - to be removed - see bug 200214
     * Returns whether an attempt should be made to preserve selection across
     * update operations. To be used by subclasses that override
     * {@link #preservingSelection(Runnable)}.
     *
     * @return <code>true</code> if selection should be preserved,
     *         <code>false</code> otherwise
     */
    boolean getPreserveSelection() {
        return this.preserveSelection;
    }

    /**
     * Hook for testing.
     * @param element
     * @return Widget
     */
    public Widget testFindItem(Object element) {
        return findItem(element);
    }

    /**
     * Hook for testing.
     * @param element
     * @return Widget[]
     * @since 3.2
     */
    public Widget[] testFindItems(Object element) {
        return findItems(element);
    }

    /**
     * Removes all elements from the map.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     */
    protected void unmapAllElements() {
        if (elementMap != null) {
            elementMap = newHashtable(CustomHashtable.DEFAULT_CAPACITY);
        }
    }

    /**
     * Removes the given element from the internal element to widget map. Does
     * nothing if mapping is disabled. If mapping is enabled, the given element
     * must be present.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param element
     *            the element
     */
    protected void unmapElement(Object element) {
        if (elementMap != null) {
            elementMap.remove(element);
        }
    }

    /**
     * Removes the given association from the internal element to widget map.
     * Does nothing if mapping is disabled, or if the given element does not map
     * to the given item.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method.
     * </p>
     *
     * @param element
     *            the element
     * @param item the item to unmap
     * @since 2.0
     */
    protected void unmapElement(Object element, Widget item) {
        // double-check that the element actually maps to the given item before
        // unmapping it
        if (elementMap != null) {
            Object widgetOrWidgets = elementMap.get(element);
            if (widgetOrWidgets == null) {
                // item was not mapped, return
                return;
            } else if (widgetOrWidgets instanceof Widget) {
                if (item == widgetOrWidgets) {
                    elementMap.remove(element);
                }
            } else {
                Widget[] widgets = (Widget[]) widgetOrWidgets;
                int indexOfItem = Arrays.asList(widgets).indexOf(item);
                if (indexOfItem == -1) {
                    return;
                }
                int length = widgets.length;
                if (indexOfItem == 0) {
                    if (length == 1) {
                        elementMap.remove(element);
                    } else {
                        Widget[] updatedWidgets = new Widget[length - 1];
                        System.arraycopy(widgets, 1, updatedWidgets, 0, length - 1);
                        elementMap.put(element, updatedWidgets);
                    }
                } else {
                    Widget[] updatedWidgets = new Widget[length - 1];
                    System.arraycopy(widgets, 0, updatedWidgets, 0, indexOfItem);
                    System.arraycopy(widgets, indexOfItem + 1, updatedWidgets, indexOfItem,
                            length - indexOfItem - 1);
                    elementMap.put(element, updatedWidgets);
                }
            }
        }
    }

    // flag to indicate that a full refresh took place. See bug 102440.
    private boolean refreshOccurred;

    /**
     * Updates the given elements' presentation when one or more of their
     * properties change. Only the given elements are updated.
     * <p>
     * This does not handle structural changes (e.g. addition or removal of
     * elements), and does not update any other related elements (e.g. child
     * elements). To handle structural changes, use the <code>refresh</code>
     * methods instead.
     * </p>
     * <p>
     * This should be called when an element has changed in the model, in order
     * to have the viewer accurately reflect the model. This method only affects
     * the viewer, not the model.
     * </p>
     * <p>
     * Specifying which properties are affected may allow the viewer to optimize
     * the update. For example, if the label provider is not affected by changes
     * to any of these properties, an update may not actually be required.
     * Specifying <code>properties</code> as <code>null</code> forces a full
     * update of the given elements.
     * </p>
     * <p>
     * If the viewer has a sorter which is affected by a change to one of the
     * properties, the elements' positions are updated to maintain the sort
     * order. Note that resorting may not happen if <code>properties</code>
     * is <code>null</code>.
     * </p>
     * <p>
     * If the viewer has a filter which is affected by a change to one of the
     * properties, elements may appear or disappear if the change affects
     * whether or not they are filtered out. Note that resorting may not happen
     * if <code>properties</code> is <code>null</code>.
     * </p>
     *
     * @param elements
     *            the elements
     * @param properties
     *            the properties that have changed, or <code>null</code> to
     *            indicate unknown
     */
    public void update(Object[] elements, String[] properties) {
        boolean previousValue = refreshOccurred;
        refreshOccurred = false;
        try {
            for (int i = 0; i < elements.length; ++i) {
                update(elements[i], properties);
                if (refreshOccurred) {
                    return;
                }
            }
        } finally {
            refreshOccurred = previousValue;
        }
    }

    /**
     * Updates the given element's presentation when one or more of its
     * properties changes. Only the given element is updated.
     * <p>
     * This does not handle structural changes (e.g. addition or removal of
     * elements), and does not update any other related elements (e.g. child
     * elements). To handle structural changes, use the <code>refresh</code>
     * methods instead.
     * </p>
     * <p>
     * This should be called when an element has changed in the model, in order
     * to have the viewer accurately reflect the model. This method only affects
     * the viewer, not the model.
     * </p>
     * <p>
     * Specifying which properties are affected may allow the viewer to optimize
     * the update. For example, if the label provider is not affected by changes
     * to any of these properties, an update may not actually be required.
     * Specifying <code>properties</code> as <code>null</code> forces a full
     * update of the element.
     * </p>
     * <p>
     * If the viewer has a sorter which is affected by a change to one of the
     * properties, the element's position is updated to maintain the sort order.
     * Note that resorting may not happen if <code>properties</code> is
     * <code>null</code>.
     * </p>
     * <p>
     * If the viewer has a filter which is affected by a change to one of the
     * properties, the element may appear or disappear if the change affects
     * whether or not the element is filtered out. Note that filtering may not
     * happen if <code>properties</code> is <code>null</code>.
     * </p>
     *
     * @param element
     *            the element
     * @param properties
     *            the properties that have changed, or <code>null</code> to
     *            indicate unknown
     */
    public void update(Object element, String[] properties) {
        Assert.isNotNull(element);
        Widget[] items = findItems(element);

        boolean mayExitEarly = !refreshOccurred;
        for (int i = 0; i < items.length; i++) {
            internalUpdate(items[i], element, properties);
            if (mayExitEarly && refreshOccurred) {
                // detected a change from refreshOccurred==false to refreshOccurred==true
                return;
            }
        }
    }

    /**
      * Updates the given element's presentation when one or more of its
      * properties changes. Only the given element is updated.
      * <p>
      * EXPERIMENTAL.  Not to be used except by JDT.
      * This method was added to support JDT's explorations
      * into grouping by working sets, which requires viewers to support multiple
      * equal elements.  See bug 76482 for more details.  This support will
      * likely be removed in Eclipse 3.3 in favor of proper support for
      * multiple equal elements (which was implemented for AbtractTreeViewer in 3.2).
      * </p>
      * @param widget
      *            the widget for the element
      * @param element
      *            the element
      * @param properties
      *            the properties that have changed, or <code>null</code> to
      *            indicate unknown
      */
    protected void internalUpdate(Widget widget, Object element, String[] properties) {
        boolean needsRefilter = false;
        if (properties != null) {
            for (int i = 0; i < properties.length; ++i) {
                needsRefilter = needsRefilter(element, properties[i]);
                if (needsRefilter) {
                    break;
                }
            }
        }
        if (needsRefilter) {
            preservingSelection(() -> {
                internalRefresh(getRoot());
                refreshOccurred = true;
            });
            return;
        }

        boolean needsUpdate;
        if (properties == null) {
            needsUpdate = true;
        } else {
            needsUpdate = false;
            IBaseLabelProvider labelProvider = getLabelProvider();
            for (int i = 0; i < properties.length; ++i) {
                needsUpdate = labelProvider.isLabelProperty(element, properties[i]);
                if (needsUpdate) {
                    break;
                }
            }
        }
        if (needsUpdate) {
            updateItem(widget, element);
        }
    }

    /**
     * Copies attributes of the given element into the given widget.
     * <p>
     * This method is internal to the framework; subclassers should not call
     * this method. Calls <code>doUpdateItem(widget, element, true)</code>.
     * </p>
     *
     * @param widget
     *            the widget
     * @param element
     *            the element
     */
    protected final void updateItem(Widget widget, Object element) {
        SafeRunnable.run(new UpdateItemSafeRunnable(widget, element, true));
    }

    /**
     * Updates the selection of this viewer.
     * <p>
     * This framework method should be called when the selection in the viewer
     * widget changes.
     * </p>
     * <p>
     * The default implementation of this method notifies all selection change
     * listeners recorded in an internal state variable. Overriding this method
     * is generally not required; however, if overriding in a subclass,
     * <code>super.updateSelection</code> must be invoked.
     * </p>
     *
     * @param selection
     *            the selection, or <code>null</code> if none
     */
    protected void updateSelection(ISelection selection) {
        SelectionChangedEvent event = new SelectionChangedEvent(this, selection);
        fireSelectionChanged(event);
    }

    /**
     * Returns whether this structured viewer is configured to use an internal
     * map to speed up the mapping between elements and SWT items.
     * <p>
     * The default implementation of this framework method checks whether the
     * internal map has been initialized.
     * </p>
     *
     * @return <code>true</code> if the element map is enabled, and
     *         <code>false</code> if disabled
     */
    protected boolean usingElementMap() {
        return elementMap != null;
    }

    @Override
    public void setLabelProvider(IBaseLabelProvider labelProvider) {
        if (labelProvider instanceof IColorProvider || labelProvider instanceof IFontProvider) {
            colorAndFontCollector = new ColorAndFontCollectorWithProviders(labelProvider);
        } else {
            colorAndFontCollector = new ColorAndFontCollector();
        }
        super.setLabelProvider(labelProvider);

    }

    /**
     * Build a label up for the element using the supplied label provider.
     * @param updateLabel The ViewerLabel to collect the result in
     * @param element The element being decorated.
     */
    protected void buildLabel(ViewerLabel updateLabel, Object element) {

        if (getLabelProvider() instanceof IViewerLabelProvider) {
            IViewerLabelProvider itemProvider = (IViewerLabelProvider) getLabelProvider();
            itemProvider.updateLabel(updateLabel, element);

            colorAndFontCollector.setUsedDecorators();

            if (updateLabel.hasNewBackground()) {
                colorAndFontCollector.setBackground(updateLabel.getBackground());
            }

            if (updateLabel.hasNewForeground()) {
                colorAndFontCollector.setForeground(updateLabel.getForeground());
            }

            if (updateLabel.hasNewFont()) {
                colorAndFontCollector.setFont(updateLabel.getFont());
            }
            return;

        }

        if (getLabelProvider() instanceof ILabelProvider) {
            ILabelProvider labelProvider = (ILabelProvider) getLabelProvider();
            updateLabel.setText(labelProvider.getText(element));
            updateLabel.setImage(labelProvider.getImage(element));
        }

    }

    /**
     * Build a label up for the element using the supplied label provider.
     * @param updateLabel The ViewerLabel to collect the result in
     * @param element The element being decorated.
     * @param labelProvider ILabelProvider the labelProvider for the receiver.
     */
    void buildLabel(ViewerLabel updateLabel, Object element, IViewerLabelProvider labelProvider) {

        labelProvider.updateLabel(updateLabel, element);

        colorAndFontCollector.setUsedDecorators();

        if (updateLabel.hasNewBackground()) {
            colorAndFontCollector.setBackground(updateLabel.getBackground());
        }

        if (updateLabel.hasNewForeground()) {
            colorAndFontCollector.setForeground(updateLabel.getForeground());
        }

        if (updateLabel.hasNewFont()) {
            colorAndFontCollector.setFont(updateLabel.getFont());
        }

    }

    /**
     * Build a label up for the element using the supplied label provider.
     * @param updateLabel The ViewerLabel to collect the result in
     * @param elementPath The path of the element being decorated.
     * @param labelProvider ILabelProvider the labelProvider for the receiver.
     */
    void buildLabel(ViewerLabel updateLabel, TreePath elementPath, ITreePathLabelProvider labelProvider) {

        labelProvider.updateLabel(updateLabel, elementPath);

        colorAndFontCollector.setUsedDecorators();

        if (updateLabel.hasNewBackground()) {
            colorAndFontCollector.setBackground(updateLabel.getBackground());
        }

        if (updateLabel.hasNewForeground()) {
            colorAndFontCollector.setForeground(updateLabel.getForeground());
        }

        if (updateLabel.hasNewFont()) {
            colorAndFontCollector.setFont(updateLabel.getFont());
        }

    }

    /**
     * Build a label up for the element using the supplied label provider.
     * @param updateLabel The ViewerLabel to collect the result in
     * @param element The element being decorated.
     * @param labelProvider ILabelProvider the labelProvider for the receiver.
     */
    void buildLabel(ViewerLabel updateLabel, Object element, ILabelProvider labelProvider) {
        updateLabel.setText(labelProvider.getText(element));
        updateLabel.setImage(labelProvider.getImage(element));
    }

    /**
     * Get the ColorAndFontCollector for the receiver.
     * @return ColorAndFontCollector
     * @since 3.1
     */
    protected ColorAndFontCollector getColorAndFontCollector() {
        return colorAndFontCollector;
    }

    @Override
    protected void handleDispose(DisposeEvent event) {
        super.handleDispose(event);
        sorter = null;
        comparer = null;
        if (filters != null)
            filters.clear();
        elementMap = newHashtable(1);
        openListeners.clear();
        doubleClickListeners.clear();
        colorAndFontCollector.clear();
        postSelectionChangedListeners.clear();
    }

}