/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.actions;

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

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;

/**
 * The abstract superclass for resource-based 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>
 */
public abstract class SelectionListenerAction
	extends BaseSelectionListenerAction {
	/**
	 * Empty list that is immutable.
	 */
	private static final List EMPTY_LIST = Arrays.asList(new Object[0]);

	/**
	 * Indicates whether the selection has changes since <code>resources</code>
	 * and <code>nonResources</code> were computed.
	 */
	private boolean selectionDirty = true;

	/**
	 * The list of resource elements in the current selection
	 * (element type: <code>IResource</code>); meaningful only when
	 * <code>selectionDirty == false</code>.
	 */
	private List resources;

	/**
	 * The list of non-resource elements in the current selection
	 * (element type: <code>Object</code>); meaningful only when
	 * <code>selectionDirty == false</code>.
	 */
	private List nonResources;

	/**
	 * 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 SelectionListenerAction(String text) {
		super(text);
	}

	/**
	 * The <code>SelectionListenerAction</code> implementation of this 
	 * <code>BaseSelectionListenerAction</code> method clears the cached
	 * resources and non-resources. 
	 */
	protected void clearCache() {
		selectionDirty = true;
		// clear out the lists in case computeResources does not get called immediately
		resources = null;
		nonResources = null;
	}

	/**
	 * Extracts <code>IResource</code>s from the current selection and adds them to
	 * the resources list, and the rest into the non-resources list.
	 */
	private final void computeResources() {
		resources = null;
		nonResources = null;

		for (Iterator e = getStructuredSelection().iterator(); e.hasNext();) {
			Object next = e.next();
			if (next instanceof IResource) {
				if (resources == null) {
					// assume selection contains mostly resources most times
					resources = new ArrayList(getStructuredSelection().size());
				}
				resources.add(next);
				continue;
			}
			if (next instanceof IAdaptable) {
				Object resource =
					((IAdaptable) next).getAdapter(IResource.class);
				if (resource != null) {
					if (resources == null) {
						// assume selection contains mostly resources most times
						resources =
							new ArrayList(getStructuredSelection().size());
					}
					resources.add(resource);
					continue;
				}
			}

			if (nonResources == null) {
				// assume selection contains mostly resources most times
				nonResources = new ArrayList(1);
			}
			nonResources.add(next);
		}
	}

	/**
	 * Returns the elements in the current selection that are not 
	 * <code>IResource</code>s.
	 *
	 * @return list of elements (element type: <code>Object</code>)
	 */
	protected List getSelectedNonResources() {
		//recompute if selection has changed.
		if (selectionDirty) {
			computeResources();
			selectionDirty = false;
		}

		if (nonResources == null)
			return EMPTY_LIST;
		else
			return nonResources;
	}

	/**
	 * Returns the elements in the current selection that are 
	 * <code>IResource</code>s.
	 *
	 * @return list of resource elements (element type: <code>IResource</code>)
	 */
	protected List getSelectedResources() {
		//recompute if selection has changed.
		if (selectionDirty) {
			computeResources();
			selectionDirty = false;
		}

		if (resources == null)
			return EMPTY_LIST;
		else
			return resources;
	}

	/**
	 * Returns whether the type of the given resource is among those in the given
	 * resource type mask.
	 * 
	 * @param resource the resource
	 * @param resourceMask a bitwise OR of resource types: 
	 *   <code>IResource</code>.{<code>FILE</code>, <code>FOLDER</code>,
	 *   <code>PROJECT</code>, <code>ROOT</code>}
	 * @return <code>true</code> if the resource type matches, and <code>false</code>
	 *   otherwise
	 * @see IResource
	 */
	protected boolean resourceIsType(IResource resource, int resourceMask) {
		return (resource.getType() & resourceMask) != 0;
	}

	/**
	 * Returns whether the current selection consists entirely of resources whose
	 * types are among those in the given resource type mask.
	 * 
	 * @param resourceMask a bitwise OR of resource types: 
	 *   <code>IResource</code>.{<code>FILE</code>, <code>FOLDER</code>,
	 *   <code>PROJECT</code>, <code>ROOT</code>}
	 * @return <code>true</code> if all resources in the current selection are of the
	 *   specified types or if the current selection is empty, and <code>false</code> if some
	 *   elements are resources of a different type or not resources
	 * @see IResource
	 */
	protected boolean selectionIsOfType(int resourceMask) {
		if (getSelectedNonResources().size() > 0)
			return false;

		for (Iterator e = getSelectedResources().iterator(); e.hasNext();) {
			IResource next = (IResource) e.next();
			if (!resourceIsType(next, resourceMask))
				return false;
		}
		return true;
	}

}
