/*******************************************************************************
 * Copyright (c) 2003, 2007 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
 *******************************************************************************/
package org.eclipse.ui.internal.navigator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;

import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.internal.navigator.dnd.NavigatorDnDService;
import org.eclipse.ui.internal.navigator.extensions.ExtensionPriorityComparator;
import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptor;
import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptorManager;
import org.eclipse.ui.internal.navigator.extensions.NavigatorContentExtension;
import org.eclipse.ui.internal.navigator.extensions.NavigatorViewerDescriptor;
import org.eclipse.ui.internal.navigator.extensions.NavigatorViewerDescriptorManager;
import org.eclipse.ui.internal.navigator.extensions.StructuredViewerManager;
import org.eclipse.ui.internal.navigator.sorters.NavigatorSorterService;
import org.eclipse.ui.navigator.IDescriptionProvider;
import org.eclipse.ui.navigator.IExtensionActivationListener;
import org.eclipse.ui.navigator.IExtensionStateModel;
import org.eclipse.ui.navigator.IMementoAware;
import org.eclipse.ui.navigator.INavigatorActivationService;
import org.eclipse.ui.navigator.INavigatorContentDescriptor;
import org.eclipse.ui.navigator.INavigatorContentExtension;
import org.eclipse.ui.navigator.INavigatorContentService;
import org.eclipse.ui.navigator.INavigatorContentServiceListener;
import org.eclipse.ui.navigator.INavigatorDnDService;
import org.eclipse.ui.navigator.INavigatorFilterService;
import org.eclipse.ui.navigator.INavigatorPipelineService;
import org.eclipse.ui.navigator.INavigatorSaveablesService;
import org.eclipse.ui.navigator.INavigatorSorterService;
import org.eclipse.ui.navigator.INavigatorViewerDescriptor;

/**
 * <p>
 * Provides centralized access to the information provided by
 * NavigatorContentExtensions. Can be instantiated as needed, but should be
 * cached for active viewers. Information specific to a given viewer will be
 * cached by the NavigatorContentService, not including ContentProviders and
 * Label Providers created by {@link #createCommonContentProvider()}and
 * {@link #createCommonLabelProvider()}respectively.
 * </p>
 * 
 * <p>
 * The following class is experimental until fully documented.
 * </p>
 */
public class NavigatorContentService implements IExtensionActivationListener,
		IMementoAware, INavigatorContentService {

	private static final NavigatorContentDescriptorManager CONTENT_DESCRIPTOR_REGISTRY = NavigatorContentDescriptorManager
			.getInstance();

	private static final NavigatorViewerDescriptorManager VIEWER_DESCRIPTOR_REGISTRY = NavigatorViewerDescriptorManager
			.getInstance();

	private static final ITreeContentProvider[] NO_CONTENT_PROVIDERS = new ITreeContentProvider[0];

	private static final ILabelProvider[] NO_LABEL_PROVIDERS = new ILabelProvider[0];

	private static final INavigatorContentDescriptor[] NO_DESCRIPTORS = new INavigatorContentDescriptor[0];

	private static final String[] NO_EXTENSION_IDS = new String[0];

	private final NavigatorViewerDescriptor viewerDescriptor;

	private final List listeners = new ArrayList();

	/*
	 * A map of (String-based-Navigator-Content-Extension-IDs,
	 * NavigatorContentExtension-objects)-pairs
	 */
	private final Map contentExtensions = new HashMap();

	private StructuredViewerManager structuredViewerManager;

	private ITreeContentProvider[] rootContentProviders;

	private WeakHashMap contributionMemory;

	private ITreeContentProvider contentProvider;

	private ILabelProvider labelProvider;

	private final VisibilityAssistant assistant;

	private NavigatorFilterService navigatorFilterService;

	private INavigatorSorterService navigatorSorterService;

	private INavigatorPipelineService navigatorPipelineService;

	private INavigatorDnDService navigatorDnDService;

	private INavigatorActivationService navigatorActivationService;

	private NavigatorSaveablesService navigatorSaveablesService;
	
	private NavigatorExtensionStateService navigatorExtensionStateService;

	private IDescriptionProvider descriptionProvider;

	private boolean contentProviderInitialized;

	private boolean labelProviderInitialized;

	/**
	 * @param aViewerId
	 *            The viewer id for this content service; normally from the
	 *            <b>org.eclipse.ui.views</b> extension.
	 */
	public NavigatorContentService(String aViewerId) {
		super();
		aViewerId = aViewerId != null ? aViewerId : ""; //$NON-NLS-1$
		viewerDescriptor = VIEWER_DESCRIPTOR_REGISTRY
				.getNavigatorViewerDescriptor(aViewerId);
		assistant = new VisibilityAssistant(viewerDescriptor, getActivationService());
		getActivationService().addExtensionActivationListener(this);
	}

	/**
	 * @param aViewerId
	 *            The viewer id for this content service; normally from the
	 *            <b>org.eclipse.ui.views</b> extension.
	 * @param aViewer
	 *            The viewer that this content service will be associated with.
	 */
	public NavigatorContentService(String aViewerId, StructuredViewer aViewer) {
		this(aViewerId);
		structuredViewerManager = new StructuredViewerManager(aViewer);
	}

	public String[] getVisibleExtensionIds() {

		List visibleExtensionIds = new ArrayList();

		NavigatorContentDescriptor[] descriptors = CONTENT_DESCRIPTOR_REGISTRY
				.getAllContentDescriptors();
		for (int i = 0; i < descriptors.length; i++) {
			if (assistant.isVisible(descriptors[i].getId())) {
				visibleExtensionIds.add(descriptors[i].getId());
			}
		}
		if (visibleExtensionIds.isEmpty()) {
			return NO_EXTENSION_IDS;
		}
		return (String[]) visibleExtensionIds
				.toArray(new String[visibleExtensionIds.size()]);

	}

	public INavigatorContentDescriptor[] getVisibleExtensions() {
		List visibleDescriptors = new ArrayList();

		NavigatorContentDescriptor[] descriptors = CONTENT_DESCRIPTOR_REGISTRY
				.getAllContentDescriptors();
		for (int i = 0; i < descriptors.length; i++) {
			if (assistant.isVisible(descriptors[i].getId())) {
				visibleDescriptors.add(descriptors[i]);
			}
		}
		if (visibleDescriptors.isEmpty()) {
			return NO_DESCRIPTORS;
		}
		return (INavigatorContentDescriptor[]) visibleDescriptors
				.toArray(new INavigatorContentDescriptor[visibleDescriptors
						.size()]);

	}

	/* package */INavigatorContentDescriptor[] getActiveDescriptorsWithSaveables() {
		List result = new ArrayList();

		NavigatorContentDescriptor[] descriptors = CONTENT_DESCRIPTOR_REGISTRY
				.getContentDescriptorsWithSaveables();
		for (int i = 0; i < descriptors.length; i++) {
			if (assistant.isVisible(descriptors[i].getId())
					&& assistant.isActive(descriptors[i])) {
				result.add(descriptors[i]);
			}
		}
		if (result.isEmpty()) {
			return NO_DESCRIPTORS;
		}
		return (INavigatorContentDescriptor[]) result
				.toArray(new INavigatorContentDescriptor[result.size()]);

	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#bindExtensions(java.lang.String[],
	 *      boolean)
	 */
	public INavigatorContentDescriptor[] bindExtensions(String[] extensionIds,
			boolean isRoot) {
		if (extensionIds == null || extensionIds.length == 0) {
			return NO_DESCRIPTORS;
		}

		for (int i = 0; i < extensionIds.length; i++) {
			assistant.bindExtensions(extensionIds, isRoot);
		}
		Set boundDescriptors = new HashSet();
		INavigatorContentDescriptor descriptor;
		for (int i = 0; i < extensionIds.length; i++) {
			descriptor = CONTENT_DESCRIPTOR_REGISTRY
					.getContentDescriptor(extensionIds[i]);
			if (descriptor != null) {
				boundDescriptors.add(descriptor);
			}
		}

		if (boundDescriptors.size() == 0) {
			return NO_DESCRIPTORS;
		}
		return (INavigatorContentDescriptor[]) boundDescriptors
				.toArray(new INavigatorContentDescriptor[boundDescriptors
						.size()]);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#createCommonContentProvider()
	 */
	public ITreeContentProvider createCommonContentProvider() {
		if (contentProviderInitialized) {
			return contentProvider;
		}
		synchronized (this) {
			if (contentProvider == null) {
				contentProvider = new NavigatorContentServiceContentProvider(
						this);
			}
			contentProviderInitialized = true;
		}
		return contentProvider;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#createCommonLabelProvider()
	 */
	public ILabelProvider createCommonLabelProvider() {
		if (labelProviderInitialized) {
			return labelProvider;
		}
		synchronized (this) {
			if (labelProvider == null) {
				labelProvider = new NavigatorContentServiceLabelProvider(this);
			}
			labelProviderInitialized = true;
		}
		return labelProvider;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#createCommonDescriptionProvider()
	 */
	public IDescriptionProvider createCommonDescriptionProvider() {
		if (descriptionProvider != null) {
			return descriptionProvider;
		}
		synchronized (this) {
			if (descriptionProvider == null) {
				descriptionProvider = new NavigatorContentServiceDescriptionProvider(
						this);
			}
		}
		return descriptionProvider;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#dispose()
	 */
	public void dispose() {
		if (navigatorSaveablesService != null) {
			assistant.removeListener(navigatorSaveablesService);
		}
		for (Iterator contentItr = contentExtensions.values().iterator(); contentItr
				.hasNext();) {
			((NavigatorContentExtension) contentItr.next()).dispose();
		}
		getActivationService().removeExtensionActivationListener(this);
		assistant.dispose();
	}

	protected void updateService(Viewer aViewer, Object anOldInput,
			Object aNewInput) {

		synchronized (this) {

			if (structuredViewerManager == null) {
				structuredViewerManager = new StructuredViewerManager(aViewer);
				structuredViewerManager.inputChanged(anOldInput, aNewInput);
			} else {
				structuredViewerManager.inputChanged(aViewer, anOldInput,
						aNewInput);
			}

			for (Iterator contentItr = contentExtensions.values().iterator(); contentItr
					.hasNext();) {
				NavigatorContentExtension ext = (NavigatorContentExtension) contentItr.next();
				if(ext.isLoaded()) {
					structuredViewerManager.initialize(ext.getContentProvider());
				}
			}

			rootContentProviders = extractContentProviders(findRootContentExtensions(aNewInput));
		}
	}

	public IExtensionStateModel findStateModel(String anExtensionId) {
		if (anExtensionId == null) {
			return null;
		}
		INavigatorContentDescriptor desc = CONTENT_DESCRIPTOR_REGISTRY
				.getContentDescriptor(anExtensionId);
		if (desc == null) {
			return null;
		}
		INavigatorContentExtension ext = getExtension(desc);
		if (ext == null) {
			return null;
		}
		return ext.getStateModel();
	}

	/**
	 * Return a set of content providers that could provide a parent for the
	 * given element. These content extensions are determined by consulting the
	 * <b>possibleChildren</b> expression in the <b>navigatorContent</b>
	 * extension.
	 * 
	 * <p>
	 * Clients that wish to tap into the link with editor support must describe
	 * all of their possible children in their <b>possibleChildren</b>
	 * expressions.
	 * </p>
	 * 
	 * @param anElement
	 *            An element from the tree (generally from a setSelection()
	 *            method).
	 * @return The set of content providers that may be able to provide a
	 *         parent.
	 */
	public ITreeContentProvider[] findParentContentProviders(Object anElement) {
		return extractContentProviders(findContentExtensionsWithPossibleChild(anElement));
	}

	/**
	 * <p>
	 * Return all of the content providers that are relevant for the viewer. The
	 * viewer is determined by the ID used to create the
	 * INavigatorContentService ({@link #getViewerId() }). See
	 * {@link #createCommonContentProvider() } for more information about how
	 * content providers are located for the root of the viewer. The root
	 * content providers are calculated once. If a new element is supplied, a
	 * client must call {@link #update() } prior in order to reset the
	 * calculated root providers.
	 * </p>
	 * 
	 * @param anElement
	 *            An element from the tree (generally the input of the viewer)
	 * @return The set of content providers that can provide root elements for a
	 *         viewer.
	 */
	public ITreeContentProvider[] findRootContentProviders(Object anElement) {
		if (rootContentProviders != null) {
			return rootContentProviders;
		}
		synchronized (this) {
			if (rootContentProviders == null) {
				rootContentProviders = extractContentProviders(findRootContentExtensions(anElement));

			}
		}
		return rootContentProviders;
	}

	/**
	 * 
	 * Return all of the label providers that are enabled for the given element.
	 * A label provider is 'enabled' if its corresponding content provider
	 * returned the element, or the element is described in the content
	 * extension's <b>triggerPoints</b> expression.
	 * 
	 * @param anElement
	 *            An element from the tree (any element contributed to the
	 *            tree).
	 * @return The set of label providers that may be able to provide a valid
	 *         (non-null) label.
	 */
	public ILabelProvider[] findRelevantLabelProviders(Object anElement) {
		return extractLabelProviders(findContentExtensionsWithPossibleChild(
				anElement, false));
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>triggerPoints</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         either declared through a
	 *         <b>org.eclipse.ui.navigator.viewer/viewerContentBinding</b> to
	 *         be a root element or have a <b>triggerPoints</b> expression that
	 *         is <i>enabled</i> for the given element.
	 */
	public Set findRootContentExtensions(Object anElement) {
		return findRootContentExtensions(anElement, true);
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>triggerPoints</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @param toRespectViewerRoots
	 *            True respect the <b>viewerContentBinding</b>s, False will
	 *            look only for matching <b>triggerPoints</b> expressions.
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         either declared through a
	 *         <b>org.eclipse.ui.navigator.viewer/viewerContentBinding</b> to
	 *         be a root element or have a <b>triggerPoints</b> expression that
	 *         is <i>enabled</i> for the given element.
	 */
	public Set findRootContentExtensions(Object anElement,
			boolean toRespectViewerRoots) {

		SortedSet rootExtensions = new TreeSet(
				ExtensionPriorityComparator.INSTANCE);
		if (toRespectViewerRoots
				&& viewerDescriptor.hasOverriddenRootExtensions()) {

			NavigatorContentDescriptor[] descriptors = CONTENT_DESCRIPTOR_REGISTRY
					.getAllContentDescriptors();

			NavigatorContentExtension extension = null;
			for (int i = 0; i < descriptors.length; i++) {
				if (isActive(descriptors[i].getId())
						&& isRootExtension(descriptors[i].getId())) {
					extension = getExtension(descriptors[i]);
					if (!extension.hasLoadingFailed()) {
						rootExtensions.add(extension);
					}
				}
			}
		}
		if (rootExtensions.isEmpty()) {
			return findContentExtensionsByTriggerPoint(anElement);
		}
		return rootExtensions;
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>possibleChildren</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>possibleChildren</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findOverrideableContentExtensionsByTriggerPoint(
			Object anElement) {
		Set overrideableExtensions = new TreeSet(
				ExtensionPriorityComparator.INSTANCE);
		Set descriptors = findDescriptorsByTriggerPoint(anElement);
		for (Iterator iter = descriptors.iterator(); iter.hasNext();) {
			INavigatorContentDescriptor descriptor = (INavigatorContentDescriptor) iter
					.next();
			if (descriptor.hasOverridingExtensions()) {
				overrideableExtensions.add(getExtension(descriptor));
			}
		}
		return overrideableExtensions;
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>possibleChildren</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>possibleChildren</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findOverrideableContentExtensionsForPossibleChild(
			Object anElement) {
		Set overrideableExtensions = new TreeSet(
				ExtensionPriorityComparator.INSTANCE);
		Set descriptors = findDescriptorsWithPossibleChild(anElement, false);
		for (Iterator iter = descriptors.iterator(); iter.hasNext();) {
			INavigatorContentDescriptor descriptor = (INavigatorContentDescriptor) iter
					.next();
			if (descriptor.hasOverridingExtensions()) {
				overrideableExtensions.add(getExtension(descriptor));
			}
		}
		return overrideableExtensions;
	}
	
	/*
	 * (Non-Javadoc)
	 *  
	 * @see INavigatorContentService#getContentDescriptorById(String)
	 */
	public INavigatorContentDescriptor getContentDescriptorById(String anExtensionId) {
		return CONTENT_DESCRIPTOR_REGISTRY.getContentDescriptor(anExtensionId); 
	}

	
	/** 
	 * 
	 * @param anExtensionId The id used to define the <b>org.eclipse.ui.navigator.navigatorContent/navigatorContent</b> extension.
	 * @return An instance of the content extension for the given extension id. May return <b>null</b> if the id is invalid.
	 */
	public INavigatorContentExtension getContentExtensionById(String anExtensionId) {
		NavigatorContentDescriptor descriptor = CONTENT_DESCRIPTOR_REGISTRY.getContentDescriptor(anExtensionId);
		if(descriptor != null)
			return getExtension(descriptor);
		return null;
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>triggerPoints</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>triggerPoints</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findContentExtensionsByTriggerPoint(Object anElement) {
		return findContentExtensionsByTriggerPoint(anElement, true);
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>triggerPoints</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @param toLoadIfNecessary
	 *            True will force the load of the extension, False will not
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>triggerPoints</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findContentExtensionsByTriggerPoint(Object anElement,
			boolean toLoadIfNecessary) {
		Set enabledDescriptors = findDescriptorsByTriggerPoint(anElement);
		return extractDescriptorInstances(enabledDescriptors, toLoadIfNecessary);
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>possibleChildren</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>possibleChildren</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findContentExtensionsWithPossibleChild(Object anElement) {
		return findContentExtensionsWithPossibleChild(anElement, true);
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>possibleChildren</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @param toLoadIfNecessary
	 *            True will force the load of the extension, False will not
	 * @return The set of {@link INavigatorContentExtension}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>possibleChildren</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findContentExtensionsWithPossibleChild(Object anElement,
			boolean toLoadIfNecessary) {
		Set enabledDescriptors = findDescriptorsWithPossibleChild(anElement);
		return extractDescriptorInstances(enabledDescriptors, toLoadIfNecessary);
	}

	/**
	 * Remember that the elements in the given array came from the given source
	 * 
	 * @param source
	 *            The descriptor of the extension that contributed the set of
	 *            elements.
	 * @param elements
	 *            An array of elements from the given source.
	 */
	public synchronized void rememberContribution(
			NavigatorContentDescriptor source, Object[] elements) {

		if (source != null && elements != null) {
			for (int i = 0; i < elements.length; i++) {
				getContributionMemory().put(elements[i], source);
			}
		}
	}

	/**
	 * Remember that the elements in the given array came from the given source
	 * 
	 * @param source
	 *            The descriptor of the extension that contributed the set of
	 *            elements.
	 * @param element
	 *            An element from the given source.
	 */
	public synchronized void rememberContribution(
			NavigatorContentDescriptor source, Object element) {
		if (source != null && element != null) {
			getContributionMemory().put(element, source);
		}
	}

	/**
	 * 
	 * @param element
	 *            The element contributed by the descriptor to be returned
	 * @return The descriptor that contributed the element or null.
	 * @see #findContentExtensionsWithPossibleChild(Object)
	 */
	public NavigatorContentDescriptor getSourceOfContribution(Object element) {
		return (NavigatorContentDescriptor) getContributionMemory()
				.get(element);
	}

	/**
	 * @return Returns the contributionMemory.
	 */
	public Map getContributionMemory() {
		if (contributionMemory != null) {
			return contributionMemory;
		}
		synchronized (this) {
			if (contributionMemory == null) {
				contributionMemory = new WeakHashMap();
			}
		}

		return contributionMemory;
	}
	
	/**
	 * Search for extensions that declare the given element in their
	 * <b>triggerPoints</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query 
	 * 			
	 * @return The set of {@link INavigatorContentDescriptor}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>triggerPoints</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findDescriptorsByTriggerPoint(Object anElement) {

		NavigatorContentDescriptor descriptor = getSourceOfContribution(anElement);
		Set result = new TreeSet(ExtensionPriorityComparator.INSTANCE);
		if (descriptor != null) {
			result.add(descriptor);
		}
		result.addAll(CONTENT_DESCRIPTOR_REGISTRY
				.findDescriptorsForTriggerPoint(anElement, assistant));
		return result;
	}

	/**
	 * Search for extensions that declare the given element in their
	 * <b>possibleChildren</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @return The set of {@link INavigatorContentDescriptor}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>possibleChildren</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findDescriptorsWithPossibleChild(Object anElement) {
		return findDescriptorsWithPossibleChild(anElement, true);
	}
	

	/**
	 * Search for extensions that declare the given element in their
	 * <b>possibleChildren</b> expression.
	 * 
	 * @param anElement
	 *            The element to use in the query
	 * @param toComputeOverrides
	 * 			 True indicates the overridden tree should be traversed.
	 * @return The set of {@link INavigatorContentDescriptor}s that are
	 *         <i>visible</i> and <i>active</i> for this content service and
	 *         have a <b>possibleChildren</b> expression that is <i>enabled</i>
	 *         for the given element.
	 */
	public Set findDescriptorsWithPossibleChild(Object anElement, boolean toComputeOverrides) {

		NavigatorContentDescriptor descriptor = getSourceOfContribution(anElement);
		Set result = new TreeSet(ExtensionPriorityComparator.INSTANCE);
		if (descriptor != null) {
			result.add(descriptor);
		}
		result.addAll(CONTENT_DESCRIPTOR_REGISTRY
				.findDescriptorsForPossibleChild(anElement, assistant, toComputeOverrides));
		return result;
	}	

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#onExtensionActivation(java.lang.String,
	 *      java.lang.String, boolean)
	 */
	public void onExtensionActivation(String aViewerId,
			String[] aNavigatorExtensionId, boolean toEnable) {
		synchronized (this) {
			try {
				NavigatorContentDescriptor key;
				NavigatorContentExtension extension;
				for (Iterator iter = contentExtensions.keySet().iterator(); iter.hasNext();) {
					key = (NavigatorContentDescriptor) iter.next();
					INavigatorActivationService activation = getActivationService();
					if(!activation.isNavigatorExtensionActive(key.getId())) {
						extension = (NavigatorContentExtension) contentExtensions.get(key);
						iter.remove();
						/* There really shouldn't be any way that this 
							can be null, but just to be safe */					
						if(extension != null) {
							extension.dispose();
						}
					}
				}
			} catch (RuntimeException e) { 
				String msg = e.getMessage() != null ? e.getMessage() : e.toString();
				NavigatorPlugin.logError(0, msg, e);
			}
		} 
		update();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#update()
	 */
	public void update() {
		rootContentProviders = null;
		if (structuredViewerManager != null) {
			structuredViewerManager.safeRefresh();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#getViewerId()
	 */
	public final String getViewerId() {
		return viewerDescriptor.getViewerId();
	}

	/**
	 * 
	 * @param aDescriptorKey
	 *            A descriptor
	 * @return The cached NavigatorContentExtension from the descriptor
	 */
	public final NavigatorContentExtension getExtension(
			INavigatorContentDescriptor aDescriptorKey) {
		return getExtension(aDescriptorKey, true);
	}

	/**
	 * 
	 * @param aDescriptorKey
	 * @param toLoadIfNecessary
	 *            True if the extension should be loaded if it is not already.
	 * @return The instance of the extension for the given descriptor key.
	 */
	public final NavigatorContentExtension getExtension(
			INavigatorContentDescriptor aDescriptorKey,
			boolean toLoadIfNecessary) {
		/* Query and return the relevant descriptor instance */
		NavigatorContentExtension extension = (NavigatorContentExtension) contentExtensions
				.get(aDescriptorKey);
		if (extension != null || !toLoadIfNecessary) {
			return extension;
		}

		/*
		 * If the descriptor instance hasn't been created yet, then we need to
		 * (1) verify that it wasn't added by another thread, (2) create and add
		 * the result into the map
		 */
		synchronized (this) {
			extension = (NavigatorContentExtension) contentExtensions
					.get(aDescriptorKey);
			if (extension == null) {
				contentExtensions.put(aDescriptorKey,
						(extension = new NavigatorContentExtension(
								(NavigatorContentDescriptor) aDescriptorKey,
								this, structuredViewerManager)));
				notifyListeners(extension);
			}
		}
		return extension;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#getViewerDescriptor()
	 */
	public INavigatorViewerDescriptor getViewerDescriptor() {
		return viewerDescriptor;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#restoreState(org.eclipse.ui.IMemento)
	 */
	public void restoreState(final IMemento aMemento) {
		synchronized (this) {
			for (Iterator extensionItr = getExtensions().iterator(); extensionItr
					.hasNext();) {
				final NavigatorContentExtension element = (NavigatorContentExtension) extensionItr
						.next();
				ISafeRunnable runnable = new ISafeRunnable() {
					public void run() throws Exception {
						element.restoreState(aMemento);

					}

					public void handleException(Throwable exception) {
						NavigatorPlugin.logError(0,
								"Could not restore state for Common Navigator content extension" //$NON-NLS-1$
										+ element.getId(), exception);

					}
				};
				SafeRunner.run(runnable);

			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#saveState(org.eclipse.ui.IMemento)
	 */
	public void saveState(IMemento aMemento) {
		synchronized (this) {
			for (Iterator extensionItr = getExtensions().iterator(); extensionItr
					.hasNext();) {
				NavigatorContentExtension element = (NavigatorContentExtension) extensionItr
						.next();
				element.saveState(aMemento);
			}
		}
	}

	public boolean isActive(String anExtensionId) {
		return assistant.isActive(anExtensionId);
	}

	public boolean isVisible(String anExtensionId) {
		return assistant.isVisible(anExtensionId);
	}

	protected final Collection getExtensions() {
		return (contentExtensions.size() > 0) ? Collections
				.unmodifiableCollection(contentExtensions.values())
				: Collections.EMPTY_LIST;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#addListener(org.eclipse.ui.internal.navigator.extensions.INavigatorContentServiceListener)
	 */
	public void addListener(INavigatorContentServiceListener aListener) {
		listeners.add(aListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#getFilterService()
	 */
	public INavigatorFilterService getFilterService() {
		if (navigatorFilterService == null) {
			navigatorFilterService = new NavigatorFilterService(this);
		}
		return navigatorFilterService;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#getFilterService()
	 */
	public INavigatorSorterService getSorterService() {
		if (navigatorSorterService == null) {
			navigatorSorterService = new NavigatorSorterService(this);
		}
		return navigatorSorterService;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#getFilterService()
	 */
	public INavigatorPipelineService getPipelineService() {
		if (navigatorPipelineService == null) {
			navigatorPipelineService = new NavigatorPipelineService(this);
		}
		return navigatorPipelineService;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#getDnDService()
	 */
	public INavigatorDnDService getDnDService() {
		if (navigatorDnDService == null) {
			navigatorDnDService = new NavigatorDnDService(this);
		}
		return navigatorDnDService;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.navigator.INavigatorContentService#getActivationService()
	 */
	public INavigatorActivationService getActivationService() {

		if (navigatorActivationService == null) {
			navigatorActivationService = new NavigatorActivationService(this);
		}
		return navigatorActivationService;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.ui.navigator.INavigatorContentService#getSaveableService()
	 */
	public INavigatorSaveablesService getSaveablesService() {
		synchronized (this) {
			if (navigatorSaveablesService == null) {
				navigatorSaveablesService = new NavigatorSaveablesService(this);
				assistant.addListener(navigatorSaveablesService);
			}
			return navigatorSaveablesService;
		}
	}
	
	/** 
	 * Not API as of 3.3. 
	 * @return The extension state service for this content service. 
	 * 
	 */
	public NavigatorExtensionStateService getExtensionStateService() {
		if (navigatorExtensionStateService == null) {
			navigatorExtensionStateService = new NavigatorExtensionStateService(this);
		}
		return navigatorExtensionStateService;
	}
	
	/**
	 * Non-API method to return a shell.
	 * @return A shell associated with the current viewer (if any) or <b>null</b>.
	 */
	public Shell getShell() {
		if(structuredViewerManager != null && structuredViewerManager.getViewer() != null) {
			return structuredViewerManager.getViewer().getControl().getShell();
		}
		return null;
	}

	protected boolean isRootExtension(String anExtensionId) {
		return assistant.isRootExtension(anExtensionId);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.internal.navigator.INavigatorContentService#removeListener(org.eclipse.ui.internal.navigator.extensions.INavigatorContentServiceListener)
	 */
	public void removeListener(INavigatorContentServiceListener aListener) {
		listeners.remove(aListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return "ContentService[" + viewerDescriptor.getViewerId() + "]"; //$NON-NLS-1$//$NON-NLS-2$
	}

	private void notifyListeners(NavigatorContentExtension aDescriptorInstance) {

		if (listeners.size() == 0) {
			return;
		}
		INavigatorContentServiceListener listener = null;
		List failedListeners = null;
		for (Iterator listenersItr = listeners.iterator(); listenersItr
				.hasNext();) {
			try {
				listener = (INavigatorContentServiceListener) listenersItr
						.next();
				listener.onLoad(aDescriptorInstance);
			} catch (RuntimeException re) {
				if (failedListeners == null) {
					failedListeners = new ArrayList();
				}
				failedListeners.add(listener);
			}
		}
		if (failedListeners != null) {
			listeners.removeAll(failedListeners);
		}
	}

	private ITreeContentProvider[] extractContentProviders(
			Set theDescriptorInstances) {
		if (theDescriptorInstances.size() == 0) {
			return NO_CONTENT_PROVIDERS;
		}
		List resultProvidersList = new ArrayList();
		for (Iterator itr = theDescriptorInstances.iterator(); itr.hasNext();) {
			resultProvidersList.add(((NavigatorContentExtension) itr.next())
					.internalGetContentProvider());
		}
		return (ITreeContentProvider[]) resultProvidersList
				.toArray(new ITreeContentProvider[resultProvidersList.size()]);
	}

	private Set extractDescriptorInstances(Set theDescriptors,
			boolean toLoadAllIfNecessary) {
		if (theDescriptors.size() == 0) {
			return Collections.EMPTY_SET;
		}
		Set resultInstances = new TreeSet(ExtensionPriorityComparator.INSTANCE);
		for (Iterator descriptorIter = theDescriptors.iterator(); descriptorIter
				.hasNext();) {
			NavigatorContentExtension extension = getExtension(
					(NavigatorContentDescriptor) descriptorIter.next(),
					toLoadAllIfNecessary);
			if (extension != null) {
				resultInstances.add(extension);

			}
		}
		return resultInstances;
	}

	private ILabelProvider[] extractLabelProviders(Set theDescriptorInstances) {
		if (theDescriptorInstances.size() == 0) {
			return NO_LABEL_PROVIDERS;
		}
		List resultProvidersList = new ArrayList();
		for (Iterator itr = theDescriptorInstances.iterator(); itr.hasNext();) {
			resultProvidersList.add(((NavigatorContentExtension) itr.next())
					.getLabelProvider());
		}
		return (ILabelProvider[]) resultProvidersList
				.toArray(new ILabelProvider[resultProvidersList.size()]);
	}



}
