/*******************************************************************************
 * 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
 *******************************************************************************/

package org.eclipse.ui.actions;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Event;

/**
 * The abstract superclass for actions that listen to selection change events.
 * This implementation tracks the current selection (see
 * <code>getStructuredSelection</code>) and provides a convenient place to
 * monitor selection changes that could affect the availability of the action.
 * <p>
 * Subclasses must implement the following <code>IAction</code> method:
 * <ul>
 *   <li><code>run</code> - to do the action's work</li>
 * </ul>
 * </p>
 * <p>
 * Subclasses may extend the <code>updateSelection</code> method to update
 * the action determine its availability based on the current selection.
 * </p>
 * <p>
 * The object instantiating the subclass is responsible for registering
 * the instance with a selection provider. Alternatively, the object can
 * notify the subclass instance directly of a selection change using the
 * methods:
 * <ul>
 *   <li><code>selectionChanged(IStructuredSelection)</code> - passing the selection</li>
 *   <li><code>selectionChanged(ISelectionChangedEvent)</code> - passing the selection change event</li>
 * </ul>
 * </p>
 * @since 3.0
 */
public abstract class BaseSelectionListenerAction extends Action implements
        ISelectionChangedListener {
    /**
     * The current selection.
     */
    private IStructuredSelection selection = new StructuredSelection();

    /**
     * Running flag:  <code>true</code> iff the action is running.
     */
    private boolean running = false;

    /**
     * The deferred selection.  Any selection change that occurs
     * while the action is running is held here until the run is complete.
     */
    private IStructuredSelection deferredSelection = null;

    /**
     * Creates a new action with the given text.
     *
     * @param text the string used as the text for the action,
     *   or <code>null</code> if there is no text
     */
    protected BaseSelectionListenerAction(String text) {
        super(text);
    }

    /**
     * Clears any cached state associated with the selection.
     * Called when the selection changes.
     * <p>
     * The <code>BaseSelectionListenerAction</code> implementation of this method
     * does nothing. Subclasses may override.
     * </p>
     */
    protected void clearCache() {
        // do nothing
    }

    /**
     * Returns the current structured selection in the workbench, or an empty
     * selection if nothing is selected or if selection does not include
     * objects (for example, raw text).
     *
     * @return the current structured selection in the workbench
     */
    public IStructuredSelection getStructuredSelection() {
        return selection;
    }

    /**
     * Notifies this action that the given structured selection has changed.
     * <p>
     * The <code>BaseSelectionListenerAction</code> implementation of this method
     * records the given selection for future reference and calls
     * <code>updateSelection</code>, updating the enable state of this action
     * based on the outcome. Subclasses should override <code>updateSelection</code>
     * to react to selection changes.
     * </p>
     *
     * @param selection the new selection
     */
    public final void selectionChanged(IStructuredSelection selection) {
        // Ignore any incoming selection change while the action is running,
        // otherwise the action can have unpredictable results, including lost
        // data, if it operates on a different selection than what it initially
        // validated.
        // See Bug 60606 [Navigator] (data loss) Navigator deletes/moves the wrong file
        if (running) {
            deferredSelection = selection;
            return;
        }
        this.selection = selection;
        clearCache();
        setEnabled(updateSelection(selection));
    }

    /**
     * The <code>BaseSelectionListenerAction</code> implementation of this
     * <code>ISelectionChangedListener</code> method calls
     * <code>selectionChanged(IStructuredSelection)</code> assuming the selection is
     * a structured one. Subclasses should override the <code>updateSelection</code>
     * method to react to selection changes.
     */
    @Override
	public final void selectionChanged(SelectionChangedEvent event) {
        ISelection selection = event.getSelection();
        if (selection instanceof IStructuredSelection) {
			selectionChanged((IStructuredSelection) selection);
		} else {
			selectionChanged(StructuredSelection.EMPTY);
		}
    }

    /**
     * Updates this action in response to the given selection.
     * <p>
     * The <code>BaseSelectionListenerAction</code> implementation of this method
     * returns <code>true</code>. Subclasses may extend to react to selection
     * changes; however, if the super method returns <code>false</code>, the
     * overriding method must also return <code>false</code>.
     * </p>
     *
     * @param selection the new selection
     * @return <code>true</code> if the action should be enabled for this selection,
     *   and <code>false</code> otherwise
     */
    protected boolean updateSelection(IStructuredSelection selection) {
        return true;
    }

    @Override
	public void runWithEvent(Event event) {
        // Set the running flag during the run so that selection changes are deferred.
        // See selectionChanged(IStructuredSelection) for more details.
        running = true;
        try {
            run();
        } finally {
            running = false;
            IStructuredSelection s = deferredSelection;
            deferredSelection = null;
            if (s != null) {
                selectionChanged(s);
            }
        }
    }
}
