/*******************************************************************************
 * Copyright (c) 2008, 2017 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.jdt.internal.ui.javaeditor;

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

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Widget;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;

import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;

import org.eclipse.jface.text.ITextSelection;

import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.ActionGroup;

import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IImportContainer;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IModularClassFile;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

import org.eclipse.jdt.ui.IWorkingCopyProvider;
import org.eclipse.jdt.ui.JavaElementComparator;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.ProblemsLabelDecorator.ProblemsLabelChangedEvent;
import org.eclipse.jdt.ui.StandardJavaElementContentProvider;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.actions.ActionUtil;
import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
import org.eclipse.jdt.internal.ui.filters.EmptyLibraryContainerFilter;
import org.eclipse.jdt.internal.ui.filters.SyntheticMembersFilter;
import org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.BreadcrumbViewer;
import org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb;
import org.eclipse.jdt.internal.ui.packageview.ClassPathContainer;
import org.eclipse.jdt.internal.ui.packageview.LibraryContainer;
import org.eclipse.jdt.internal.ui.packageview.PackageFragmentRootContainer;
import org.eclipse.jdt.internal.ui.util.JavaUIHelp;
import org.eclipse.jdt.internal.ui.viewsupport.AppearanceAwareLabelProvider;
import org.eclipse.jdt.internal.ui.viewsupport.DecoratingJavaLabelProvider;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
import org.eclipse.jdt.internal.ui.viewsupport.ResourceToItemsMapper;


/**
 * The breadcrumb for the Java editor. Shows Java elements. Requires a Java editor.
 *
 * @since 3.4
 */
public class JavaEditorBreadcrumb extends EditorBreadcrumb {

	private static final boolean SHOW_LIBRARIES_NODE= true;


	private static class ProblemBreadcrumbViewer extends BreadcrumbViewer implements ResourceToItemsMapper.IContentViewerAccessor {

		private ResourceToItemsMapper fResourceToItemsMapper;

		public ProblemBreadcrumbViewer(Composite parent, int style) {
			super(parent, style);
			fResourceToItemsMapper= new ResourceToItemsMapper(this);
		}

		/*
		 * @see org.eclipse.jdt.internal.ui.viewsupport.ResourceToItemsMapper.IContentViewerAccessor#doUpdateItem(org.eclipse.swt.widgets.Widget)
		 */
		@Override
		public void doUpdateItem(Widget item) {
			doUpdateItem(item, item.getData(), true);
		}

		/*
		 * @see StructuredViewer#mapElement(Object, Widget)
		 */
		@Override
		protected void mapElement(Object element, Widget item) {
			super.mapElement(element, item);
			if (item instanceof Item) {
				fResourceToItemsMapper.addToMap(element, (Item) item);
			}
		}

		/*
		 * @see StructuredViewer#unmapElement(Object, Widget)
		 */
		@Override
		protected void unmapElement(Object element, Widget item) {
			if (item instanceof Item) {
				fResourceToItemsMapper.removeFromMap(element, (Item) item);
			}
			super.unmapElement(element, item);
		}

		/*
		 * @see StructuredViewer#unmapAllElements()
		 */
		@Override
		protected void unmapAllElements() {
			fResourceToItemsMapper.clearMap();
			super.unmapAllElements();
		}

		/*
		 * @see org.eclipse.jface.viewers.StructuredViewer#handleLabelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
		 */
		@Override
		protected void handleLabelProviderChanged(LabelProviderChangedEvent event) {
			if (event instanceof ProblemsLabelChangedEvent) {
				ProblemsLabelChangedEvent e= (ProblemsLabelChangedEvent) event;
				if (!e.isMarkerChange() && canIgnoreChangesFromAnnotionModel()) {
					return;
				}
			}

			Object[] changed= event.getElements();
			if (changed != null && !fResourceToItemsMapper.isEmpty()) {
				ArrayList<Object> others= new ArrayList<>(changed.length);
				for (Object curr : changed) {
					if (curr instanceof IResource) {
						fResourceToItemsMapper.resourceChanged((IResource) curr);
					} else {
						others.add(curr);
					}
				}
				if (others.isEmpty()) {
					return;
				}
				event= new LabelProviderChangedEvent((IBaseLabelProvider) event.getSource(), others.toArray());
			}
			super.handleLabelProviderChanged(event);
		}

		/**
		 * Answers whether this viewer can ignore label provider changes resulting from marker
		 * changes in annotation models.
		 *
		 * @return <code>true</code> if annotation model changes can be ignored
		 */
		private boolean canIgnoreChangesFromAnnotionModel() {
			Object contentProvider= getContentProvider();
			return contentProvider instanceof IWorkingCopyProvider && !((IWorkingCopyProvider) contentProvider).providesWorkingCopies();
		}

		/*
		 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.BreadcrumbViewer#configureDropDownViewer(org.eclipse.jface.viewers.TreeViewer, java.lang.Object)
		 */
		@Override
		public void configureDropDownViewer(TreeViewer viewer, Object input) {
			viewer.setContentProvider(createDropDownContentProvider());
			viewer.setLabelProvider(createDropDownLabelProvider());
			viewer.setComparator(new JavaElementComparator());
			viewer.addFilter(new SyntheticMembersFilter());
			viewer.addFilter(new ViewerFilter() {
				@Override
				public boolean select(Viewer viewer1, Object parentElement, Object element) {
					if (element instanceof IMember) {
						if (((IMember) element).getElementName().startsWith("<")) { //$NON-NLS-1$
							// filter out <clinit>
							return false;
						}
					}

					return true;
				}
			});
			if (SHOW_LIBRARIES_NODE)
				viewer.addFilter(new EmptyLibraryContainerFilter());
			JavaUIHelp.setHelp(viewer, IJavaHelpContextIds.JAVA_EDITOR_BREADCRUMB);
		}

		private ILabelProvider createDropDownLabelProvider() {
			final AppearanceAwareLabelProvider result= new AppearanceAwareLabelProvider(AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS | JavaElementLabels.F_APP_TYPE_SIGNATURE
					| JavaElementLabels.ALL_CATEGORY | JavaElementLabels.P_COMPRESSED, JavaElementImageProvider.SMALL_ICONS | AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS);

			return new DecoratingJavaLabelProvider(result);
		}
	}

	private static final class JavaEditorBreadcrumbContentProvider implements ITreeContentProvider {

		private final StandardJavaElementContentProvider fParent;
		private Object[] fElements;
		private Object fLastInputElement;

		public JavaEditorBreadcrumbContentProvider(StandardJavaElementContentProvider parent) {
			fParent= parent;
		}

		/*
		 * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
		 */
		@Override
		public Object[] getElements(Object inputElement) {
			return getChildren(inputElement);
		}

		/*
		 * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
		 */
		@Override
		public Object[] getChildren(Object inputElement) {
			if (inputElement == fLastInputElement)
				return fElements;

			fLastInputElement= inputElement;
			if (inputElement instanceof IPackageFragment) {
				fElements= getPackageContent((IPackageFragment) inputElement);
			} else if (inputElement instanceof IProject) {
				IProject project= (IProject) inputElement;
				if (project.isAccessible()) {
					try {
						fElements= ((IProject) inputElement).members();
					} catch (CoreException e) {
						JavaPlugin.log(e);
					}
				} else {
					fElements= new Object[0];
				}
			} else if (inputElement instanceof IPackageFragmentRoot) {

				ArrayList<Object> packages= new ArrayList<>();
				for (Object object : fParent.getChildren(inputElement)) {
					if (object instanceof IPackageFragment) {
						try {
							if (((IPackageFragment) object).hasChildren())
								packages.add(object);
						} catch (JavaModelException e) {
							JavaPlugin.log(e);
							packages.add(object);
						}
					} else if (object instanceof ITypeRoot && JavaModelUtil.isModuleInfo((ITypeRoot) object)) {
						try {
							packages.add(((ITypeRoot) object).getModule()); // skip the compilation unit
						} catch (JavaModelException e) {
							JavaPlugin.log(e);
							packages.add(object);
						}
					} else {
						packages.add(object);
					}
				}
				fElements= packages.toArray();
			} else if (inputElement instanceof IJavaModel) {
				fElements= getAccessibleProjects((IJavaModel)inputElement);
			} else {
				fElements= fParent.getChildren(inputElement);
			}

			return fElements;
		}

		/**
		 * Returns all accessible projects of the given Java model.
		 *
		 * @param model the Java model
		 * @return the accessible projects of the given model
		 */
		private Object[] getAccessibleProjects(IJavaModel model) {
			IJavaProject[] javaProjects;
			Object[] nonJavaResources;
			try {
				javaProjects= model.getJavaProjects();
				nonJavaResources= model.getNonJavaResources();
			} catch (JavaModelException e) {
				return fParent.getChildren(model);
			}
			ArrayList<IAdaptable> result= new ArrayList<>(javaProjects.length + nonJavaResources.length);
			for (Object nonJavaResource : nonJavaResources) {
				IProject project= (IProject) nonJavaResource;
				if (project.isAccessible())
					result.add(project);
			}
			for (IJavaProject javaProject : javaProjects) {
				if (javaProject.getProject().isAccessible())
					result.add(javaProject);
			}
			return result.toArray(new Object[result.size()]);
		}

		/*
		 * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
		 */
		@Override
		public Object getParent(Object element) {
			if (element instanceof IType && ((IType) element).isBinary()) {
				IType declaringType= ((IType) element).getDeclaringType();
				if (declaringType != null)
					return declaringType;
			}
			if (element instanceof IModuleDescription) {
				IJavaElement parent= ((IModuleDescription) element).getParent();
				if (parent instanceof ITypeRoot && parent.getParent().exists()) // no containing package if not on the classpath
					element= parent;
			}

			Object result= fParent.getParent(element);

			if (result instanceof ITypeRoot) {
				if (ActionUtil.isOnBuildPath((IJavaElement) result)) {
					result= fParent.getParent(result);
				} else {
					result= ((ITypeRoot) result).getResource();
					if (result instanceof IFile)
						result= fParent.getParent(result);
				}
			}

			return result;
		}

		/*
		 * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
		 */
		@Override
		public boolean hasChildren(Object element) {
			if (element instanceof IProject) {
				IProject project= (IProject) element;
				if (!project.isAccessible()) {
					return false;
				}
				try {
					return project.members().length > 0;
				} catch (CoreException e) {
					JavaPlugin.log(e);
				}
				return false;
			} else {
				return fParent.hasChildren(element);
			}
		}

		private Object[] getPackageContent(IPackageFragment pack) {
			ArrayList<Object> result= new ArrayList<>();
			try {
				for (ICompilationUnit unit : pack.getCompilationUnits()) {
					if (JavaModelUtil.isPackageInfo(unit)) {
						result.add(unit);
					}
					for (IType type : unit.getTypes()) {
						if (isValidType(type)) {
							result.add(type);
						}
					}
				}

				for (IOrdinaryClassFile classFile : pack.getOrdinaryClassFiles()) {
					if (isValidType(classFile.getType())) {
						result.add(classFile.getType());
					}
				}

				Object[] nonJavaResources= pack.getNonJavaResources();
				result.addAll(Arrays.asList(nonJavaResources));
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}

			return result.toArray();
		}

		private boolean isValidType(IType type) {
			if (type.getDeclaringType() != null)
				return false;

			try {
				return !type.isAnonymous();
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
				return false;
			}
		}

		/*
		 * @see org.eclipse.jface.viewers.IContentProvider#dispose()
		 */
		@Override
		public void dispose() {
			fParent.dispose();
		}

		/*
		 * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
		 */
		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			fElements= null;
			fLastInputElement= null;
			fParent.inputChanged(viewer, oldInput, newInput);
		}
	}

	private class ElementChangeListener implements IElementChangedListener {

		private Runnable fRunnable;

		/*
		 * @seeorg.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.
		 * ElementChangedEvent)
		 */
		@Override
		public void elementChanged(ElementChangedEvent event) {
			if (fViewer == null)
				return;

			Object input= fViewer.getInput();
			if (!(input instanceof IJavaElement))
				return;

			if (fRunnable != null)
				return;

			final IJavaElement changedElement= getChangedParentElement((IJavaElement) input, event.getDelta());
			if (changedElement == null)
				return;

			fRunnable= () -> {
				if (fViewer == null)
					return;

				Object newInput= getCurrentInput();
				if (newInput instanceof IJavaElement)
					newInput= getInput((IJavaElement) newInput);

				fViewer.setInput(newInput);
				fRunnable= null;
			};
			fViewer.getControl().getDisplay().asyncExec(fRunnable);
		}

		/**
		 * Returns the most generic ancestor of the given input which has a change, or <b>null</b>
		 * if no such ancestor exists.
		 *
		 * @param input the input of which the result must be an ancestor
		 * @param delta the delta describing the model change
		 * @return the changed element or <code>null</code>
		 */
		private IJavaElement getChangedParentElement(IJavaElement input, IJavaElementDelta delta) {
			IJavaElement element= delta.getElement();

			if (!isAncestor(element, input))
				return null;

			if (element instanceof ICompilationUnit) {
				ICompilationUnit cu= (ICompilationUnit) element;
				if (!cu.getPrimary().equals(cu))
					return null;

				if (isStructuralChange(delta))
					return element;
			} else {
				if (!onlyChildrenChanged(delta))
					return element;
			}

			for (IJavaElementDelta affectedChild : delta.getAffectedChildren()) {
				IJavaElement res= getChangedParentElement(input, affectedChild);
				if (res != null)
					return res;
			}

			return null;
		}

		/**
		 * Tells whether the given element is an ancestor of the given input.
		 *
		 * @param element the element which might be a parent
		 * @param input the element to resolve the parent chain for
		 * @return <code>true</code> if <code>element</code> is a parent of <code>input</code>
		 */
		private boolean isAncestor(IJavaElement element, IJavaElement input) {
			while (input != null && !input.equals(element)) {
				input= input.getParent();
			}

			return input != null;
		}

		private boolean isStructuralChange(IJavaElementDelta delta) {
			if (delta.getKind() != IJavaElementDelta.CHANGED)
				return true;

			return (delta.getFlags() & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_FINE_GRAINED)) == IJavaElementDelta.F_CONTENT;
		}

		private boolean onlyChildrenChanged(IJavaElementDelta delta) {
			if (delta.getKind() != IJavaElementDelta.CHANGED)
				return false;

			return (delta.getFlags() & ~IJavaElementDelta.F_FINE_GRAINED) == IJavaElementDelta.F_CHILDREN;
		}
	}


	private ActionGroup fBreadcrumbActionGroup;
	private BreadcrumbViewer fViewer;
	private ISelection fEditorSelection;
	private ElementChangeListener fElementChangeListener;


	public JavaEditorBreadcrumb(JavaEditor javaEditor) {
		super(javaEditor);
		setTextViewer(javaEditor.getViewer());
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#activateBreadcrumb()
	 */
	@Override
	protected void activateBreadcrumb() {
		fEditorSelection= getJavaEditor().getSelectionProvider().getSelection();
		IEditorSite editorSite= getJavaEditor().getEditorSite();
		editorSite.getKeyBindingService().setScopes(new String[] { "org.eclipse.jdt.ui.breadcrumbEditorScope" }); //$NON-NLS-1$
		getJavaEditor().setActionsActivated(false);
		fBreadcrumbActionGroup.fillActionBars(editorSite.getActionBars());
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#deactivateBreadcrumb()
	 */
	@Override
	protected void deactivateBreadcrumb() {
		IEditorSite editorSite= getJavaEditor().getEditorSite();
		editorSite.getKeyBindingService().setScopes(new String[] { "org.eclipse.jdt.ui.javaEditorScope" }); //$NON-NLS-1$
		getJavaEditor().getActionGroup().fillActionBars(editorSite.getActionBars());
		getJavaEditor().setActionsActivated(true);
		fEditorSelection= null;
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#createViewer(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	protected BreadcrumbViewer createViewer(Composite composite) {
		fViewer= new ProblemBreadcrumbViewer(composite, SWT.HORIZONTAL);

		fViewer.setLabelProvider(createLabelProvider());
		fViewer.setToolTipLabelProvider(createToolTipLabelProvider());

		fViewer.setContentProvider(createContentProvider());
		fViewer.addSelectionChangedListener(event -> fBreadcrumbActionGroup.setContext(new ActionContext(fViewer.getSelection())));

		fBreadcrumbActionGroup= new JavaEditorBreadcrumbActionGroup(getJavaEditor(), fViewer);

		fElementChangeListener= new ElementChangeListener();
		JavaCore.addElementChangedListener(fElementChangeListener);

		JavaUIHelp.setHelp(fViewer, IJavaHelpContextIds.JAVA_EDITOR_BREADCRUMB);

		return fViewer;
	}

	/**
	 * Create a new instance of the content provider to use for the Java editor breadcrumb.
	 *
	 * @return a new content provider
	 */
	private static JavaEditorBreadcrumbContentProvider createContentProvider() {
		StandardJavaElementContentProvider parentContentProvider= new StandardJavaElementContentProvider(true);
		return new JavaEditorBreadcrumbContentProvider(parentContentProvider);
	}

	/**
	 * Create a new instance of the content provider to use for the Java editor breadcrumb.
	 *
	 * @return a new content provider
	 * @since 3.5
	 */
	private static JavaEditorBreadcrumbContentProvider createDropDownContentProvider() {
		StandardJavaElementContentProvider parentContentProvider= new StandardJavaElementContentProvider(true) {
			@Override
			public Object[] getChildren(Object element) {
				if (element instanceof PackageFragmentRootContainer)
					return getContainerPackageFragmentRoots((PackageFragmentRootContainer)element);
				return super.getChildren(element);
			}

			@Override
			protected Object[] getPackageFragmentRoots(IJavaProject project) throws JavaModelException {
				if (!project.getProject().isOpen())
					return NO_CHILDREN;

				List<Object> result= new ArrayList<>();

				for (IPackageFragmentRoot root : project.getPackageFragmentRoots()) {
					IClasspathEntry classpathEntry= JavaModelUtil.getClasspathEntry(root);
					int entryKind= classpathEntry.getEntryKind();
					if (entryKind != IClasspathEntry.CPE_CONTAINER // all ClassPathContainers are added later
							&& (!SHOW_LIBRARIES_NODE || (entryKind != IClasspathEntry.CPE_LIBRARY && entryKind != IClasspathEntry.CPE_VARIABLE))) { // skip: will add the referenced library node later
						if (isProjectPackageFragmentRoot(root)) {
							// filter out package fragments that correspond to projects and
							// replace them with the package fragments directly
							Object[] fragments= getPackageFragmentRootContent(root);
							result.addAll(Arrays.asList(fragments));
						} else {
							result.add(root);
						}
					}
				}

				if (SHOW_LIBRARIES_NODE) {
					result.add(new LibraryContainer(project));
				}

				// separate loop to make sure all containers are on the classpath
				for (IClasspathEntry classpathEntry : project.getRawClasspath()) {
					if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
						result.add(new ClassPathContainer(project, classpathEntry));
					}
				}
				Object[] resources= project.getNonJavaResources();
				result.addAll(Arrays.asList(resources));
				return result.toArray();
			}

			private Object[] getContainerPackageFragmentRoots(PackageFragmentRootContainer container) {
				return container.getChildren();
			}

			@Override
			protected Object internalGetParent(Object element) {
				if (element instanceof IPackageFragmentRoot) {
					// since we insert logical package containers we have to fix
					// up the parent for package fragment roots so that they refer
					// to the container and containers refer to the project
					IPackageFragmentRoot root= (IPackageFragmentRoot)element;

					try {
						IClasspathEntry entry= root.getRawClasspathEntry();
						int entryKind= entry.getEntryKind();
						if (entryKind == IClasspathEntry.CPE_CONTAINER) {
							return new ClassPathContainer(root.getJavaProject(), entry);
						} else if (SHOW_LIBRARIES_NODE && (entryKind == IClasspathEntry.CPE_LIBRARY || entryKind == IClasspathEntry.CPE_VARIABLE)) {
							return new LibraryContainer(root.getJavaProject());
						}
					} catch (JavaModelException e) {
						// fall through
					}
				} else if (element instanceof PackageFragmentRootContainer) {
					return ((PackageFragmentRootContainer)element).getJavaProject();
				}
				return super.internalGetParent(element);
			}


		};
		return new JavaEditorBreadcrumbContentProvider(parentContentProvider);
	}

	/**
	 * Create a new instance of the label provider to use for the Java editor breadcrumb.
	 *
	 * @return a new label provider
	 */
	private static ILabelProvider createLabelProvider() {
		final AppearanceAwareLabelProvider result= new AppearanceAwareLabelProvider(JavaElementLabels.ROOT_VARIABLE | JavaElementLabels.T_TYPE_PARAMETERS | JavaElementLabels.M_PARAMETER_TYPES
				| JavaElementLabels.M_APP_TYPE_PARAMETERS | JavaElementLabels.M_APP_RETURNTYPE | JavaElementLabels.F_APP_TYPE_SIGNATURE
				| JavaElementLabels.ALL_CATEGORY | JavaElementLabels.P_COMPRESSED, JavaElementImageProvider.SMALL_ICONS | AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS);

		return new DecoratingJavaLabelProvider(result) {

			/*
			 * @see
			 * org.eclipse.jdt.internal.ui.viewsupport.ColoringLabelProvider#getText(java.lang.Object
			 * )
			 */
			@Override
			public String getText(Object element) {
				if (element instanceof IPackageFragmentRoot) {
					IPackageFragmentRoot root= (IPackageFragmentRoot) element;
					if (root.isArchive() && root.isExternal()) {
						return JavaElementLabels.getElementLabel(root, JavaElementLabels.ALL_DEFAULT);
					}
				}

				return result.getText(element);
			}

			/*
			 * @see org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider#getStyledText(java.lang.Object)
			 */
			@Override
			protected StyledString getStyledText(Object element) {
				return new StyledString(getText(element));
			}
		};
	}

	/**
	 * Returns the label provider to use for the tool tips.
	 *
	 * @return a label provider for the tool tips
	 */
	private ILabelProvider createToolTipLabelProvider() {
		final AppearanceAwareLabelProvider result= new AppearanceAwareLabelProvider(AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS | JavaElementLabels.F_APP_TYPE_SIGNATURE
				| JavaElementLabels.ALL_CATEGORY, JavaElementImageProvider.SMALL_ICONS | AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS);

		return new DecoratingJavaLabelProvider(result);
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#dispose()
	 */
	@Override
	public void dispose() {
		super.dispose();

		if (fViewer != null) {
			fBreadcrumbActionGroup.dispose();
			JavaCore.removeElementChangedListener(fElementChangeListener);
			fViewer= null;
		}
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#createContextMenuActionGroup(org.eclipse.jface.viewers.ISelectionProvider)
	 */
	@Override
	protected ActionGroup createContextMenuActionGroup(ISelectionProvider selectionProvider) {
		return new JavaEditorBreadcrumbActionGroup(getJavaEditor(), selectionProvider);
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#setInput(java.lang.Object)
	 */
	@Override
	public void setInput(Object element) {
		if (element == null) {
			element= getCurrentInput();
			if (element instanceof IType) {
				element= ((IType) element).getDeclaringType();
			}
		}

		if (element instanceof IJavaElement) {
			super.setInput(getInput((IJavaElement) element));
		} else {
			super.setInput(element);
		}
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#getCurrentInput()
	 */
	@Override
	protected Object getCurrentInput() {
		try {
			ITypeRoot input= SelectionConverter.getInput(getJavaEditor());
			if (input == null)
				return null;

			ITextSelection selection;
			if (fEditorSelection instanceof ITextSelection) {
				selection= (ITextSelection) fEditorSelection;
			} else {
				selection= (ITextSelection) getJavaEditor().getSelectionProvider().getSelection();
			}
			return getInput(SelectionConverter.getElementAtOffset(input, selection));
		} catch (JavaModelException e) {
			return null;
		}
	}

	/**
	 * Returns the input for the given element. The Java breadcrumb does not show some elements of
	 * the model:
	 * <ul>
	 * 		<li><code>ITypeRoots</li>
	 * 		<li><code>IPackageDeclaration</li>
	 * 		<li><code>IImportContainer</li>
	 * 		<li><code>IImportDeclaration</li>
	 * </ul>
	 *
	 * @param element the potential input element
	 * @return the element to use as input
	 */
	private IJavaElement getInput(IJavaElement element) {
		try {
			if (element instanceof IImportDeclaration)
				element= element.getParent();

			if (element instanceof IImportContainer)
				element= element.getParent();

			if (element instanceof IPackageDeclaration)
				element= element.getParent();

			if (element instanceof ICompilationUnit) {
				IType[] types= ((ICompilationUnit) element).getTypes();
				if (types.length > 0)
					element= types[0];
			}

			if (element instanceof IOrdinaryClassFile)
				element= ((IOrdinaryClassFile) element).getType();

			if (element instanceof IModularClassFile)
				element= ((IModularClassFile) element).getModule();

			return element;
		} catch (JavaModelException e) {
			return null;
		}
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#open(java.lang.Object)
	 */
	@Override
	protected boolean open(Object element) {
		if (element instanceof IFile)
			return openInNewEditor(element);

		if (element instanceof IJarEntryResource) {
			if (((IJarEntryResource)element).isFile())
				return openInNewEditor(element);
			return false;
		}


		if (!(element instanceof IJavaElement))
			return false;

		IJavaElement javaElement= (IJavaElement) element;

		ITypeRoot root= (ITypeRoot) javaElement.getAncestor(IJavaElement.COMPILATION_UNIT);
		if (root == null)
			root= (ITypeRoot) javaElement.getAncestor(IJavaElement.CLASS_FILE);

		if (root == null)
			return false;

		return openInNewEditor(element);
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.EditorBreadcrumb#reveal(java.lang.Object)
	 */
	@Override
	protected boolean reveal(Object element) {
		if (!(element instanceof IJavaElement))
			return false;

		IJavaElement javaElement= (IJavaElement) element;

		ITypeRoot inputElement= EditorUtility.getEditorInputJavaElement(getJavaEditor(), false);

		ITypeRoot root= (ITypeRoot) javaElement.getAncestor(IJavaElement.COMPILATION_UNIT);
		if (root == null)
			root= (ITypeRoot) javaElement.getAncestor(IJavaElement.CLASS_FILE);

		if (root == null)
			return false;

		if (!root.equals(inputElement))
			return false;

		return revealInEditor(javaElement);
	}

	private boolean openInNewEditor(Object element) {
		try {
			IEditorPart newEditor= EditorUtility.openInEditor(element);
			if (newEditor != null && element instanceof IJavaElement)
				EditorUtility.revealInEditor(newEditor, (IJavaElement) element);

			return true;
		} catch (PartInitException e) {
			JavaPlugin.log(e);
			return false;
		}
	}

	private boolean revealInEditor(IJavaElement element) {
		EditorUtility.revealInEditor(getJavaEditor(), element);
		return true;
	}

	private JavaEditor getJavaEditor() {
		return (JavaEditor)getTextEditor();
	}

}
