/*******************************************************************************
 * Copyright (c) 2004, 2011 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
 *     James Blackburn (Broadcom Corp.) - ongoing development
 *******************************************************************************/
package org.eclipse.core.resources.mapping;

import java.util.ArrayList;
import org.eclipse.core.internal.resources.MarkerManager;
import org.eclipse.core.internal.resources.Workspace;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;

/**
 * A resource traversal is simply a set of resources and the depth to which
 * each is to be traversed. A set of traversals is used to describe the
 * resources that constitute a model element.
 * <p>
 * The flags of the traversal indicate which special resources should be
 * included or excluded from the traversal. The flags used are the same as
 * those passed to the {@link IResource#accept(IResourceVisitor, int, int)} method.
 * 
 * <p>
 * This class may be instantiated or subclassed by clients.
 * </p>

 * @see org.eclipse.core.resources.IResource
 * @since 3.2
 */
public class ResourceTraversal {

	private final int depth;
	private final int flags;
	private final IResource[] resources;

	/**
	 * Creates a new resource traversal.
	 * @param resources The resources in the traversal
	 * @param depth The traversal depth
	 * @param flags the flags for this traversal. The traversal flags match those
	 * that are passed to the <code>IResource#accept</code> method.
	 */
	public ResourceTraversal(IResource[] resources, int depth, int flags) {
		if (resources == null)
			throw new NullPointerException();
		this.resources = resources;
		this.depth = depth;
		this.flags = flags;
	}

	/**
	 * Visits all existing resources defined by this traversal.
	 * 
	 * @param visitor a resource visitor
	 * @exception CoreException if this method fails. Reasons include:
	 * <ul>
	 * <li> The visitor failed with this exception.</li>
	 * </ul>
	 */
	public void accept(IResourceVisitor visitor) throws CoreException {
		for (int i = 0, imax = resources.length; i < imax; i++)
			try {
				if (resources[i].exists())
					resources[i].accept(visitor, depth, flags);
			} catch (CoreException e) {
				//ignore failure in the case of concurrent deletion
				if (e.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND)
					throw e;
			}
	}

	/**
	 * Return whether the given resource is contained in or
	 * covered by this traversal, regardless of whether the resource
	 * currently exists.
	 * 
	 * @param resource the resource to be tested
	 * @return <code>true</code> if the resource is in this traversal, and 
	 * <code>false</code> otherwise.
	 */
	public boolean contains(IResource resource) {
		for (int i = 0; i < resources.length; i++) {
			IResource member = resources[i];
			if (contains(member, resource)) {
				return true;
			}
		}
		return false;
	}

	private boolean contains(IResource resource, IResource child) {
		if (resource.equals(child))
			return true;
		if (depth == IResource.DEPTH_ZERO)
			return false;
		if (child.getParent().equals(resource))
			return true;
		if (depth == IResource.DEPTH_INFINITE)
			return resource.getFullPath().isPrefixOf(child.getFullPath());
		return false;
	}

	/**
	 * Efficient implementation of {@link #findMarkers(String, boolean)}, not
	 * available to clients because underlying non-API methods are used that
	 * may change.
	 */
	void doFindMarkers(ArrayList<IMarker> result, String type, boolean includeSubtypes) {
		MarkerManager markerMan = ((Workspace) ResourcesPlugin.getWorkspace()).getMarkerManager();
		for (int i = 0; i < resources.length; i++)
			markerMan.doFindMarkers(resources[i], result, type, includeSubtypes, depth);
	}

	/**
	 * Returns all markers of the specified type on existing resources in this traversal.
	 * If <code>includeSubtypes</code> is <code>false</code>, only markers 
	 * whose type exactly matches the given type are returned.  Returns an empty 
	 * array if there are no matching markers.
	 *
	 * @param type the type of marker to consider, or <code>null</code> to indicate all types
	 * @param includeSubtypes whether or not to consider sub-types of the given type
	 * @return an array of markers
	 * @exception CoreException if this method fails.
	 * @see IResource#findMarkers(String, boolean, int)
	 */
	public IMarker[] findMarkers(String type, boolean includeSubtypes) throws CoreException {
		if (resources.length == 0)
			return new IMarker[0];
		ArrayList<IMarker> result = new ArrayList<IMarker>();
		doFindMarkers(result, type, includeSubtypes);
		return result.toArray(new IMarker[result.size()]);
	}

	/**
	 * Returns the depth to which the resources should be traversed.
	 * 
	 * @return the depth to which the physical resources are to be traversed
	 * (one of IResource.DEPTH_ZERO, IResource.DEPTH_ONE or
	 * IResource.DEPTH_INFINITE)
	 */
	public int getDepth() {
		return depth;
	}

	/**
	 * Return the flags for this traversal. 
	 * The flags of the traversal indicate which special resources should be
	 * included or excluded from the traversal. The flags used are the same as
	 * those passed to the <code>IResource#accept(IResourceVisitor, int, int)</code> method.
	 * Clients who traverse the resources manually (i.e. without calling <code>accept</code>)
	 * should respect the flags when determining which resources are included
	 * in the traversal.
	 * 
	 * @return the flags for this traversal
	 */
	public int getFlags() {
		return flags;
	}

	/**
	 * Returns the file system resource(s) for this traversal. The returned
	 * resources must be contained within the same project and need not exist in
	 * the local file system. The traversal of the returned resources should be
	 * done considering the flag returned by getDepth. If a resource returned by
	 * a traversal is a file, it should always be visited. If a resource of a
	 * traversal is a folder then files contained in the folder can only be
	 * visited if the folder is IResource.DEPTH_ONE or IResource.DEPTH_INFINITE.
	 * Child folders should only be visited if the depth is
	 * IResource.DEPTH_INFINITE.
	 * 
	 * @return The resources in this traversal
	 */
	public IResource[] getResources() {
		return resources;
	}
}
