/*******************************************************************************
 * Copyright (c) 2000, 2004 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 net.sourceforge.phpeclipse.phpeditor;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Vector;

import net.sourceforge.phpdt.core.ElementChangedEvent;
import net.sourceforge.phpdt.core.ICompilationUnit;
import net.sourceforge.phpdt.core.IElementChangedListener;
import net.sourceforge.phpdt.core.IField;
import net.sourceforge.phpdt.core.IJavaElement;
import net.sourceforge.phpdt.core.IJavaElementDelta;
import net.sourceforge.phpdt.core.IMember;
import net.sourceforge.phpdt.core.IMethod;
import net.sourceforge.phpdt.core.IParent;
import net.sourceforge.phpdt.core.ISourceRange;
import net.sourceforge.phpdt.core.ISourceReference;
import net.sourceforge.phpdt.core.IType;
import net.sourceforge.phpdt.core.JavaCore;
import net.sourceforge.phpdt.core.JavaModelException;
import net.sourceforge.phpdt.internal.corext.util.JavaModelUtil;
import net.sourceforge.phpdt.internal.ui.IJavaHelpContextIds;
import net.sourceforge.phpdt.internal.ui.PHPUiImages;
import net.sourceforge.phpdt.internal.ui.actions.AbstractToggleLinkingAction;
import net.sourceforge.phpdt.internal.ui.actions.CompositeActionGroup;
import net.sourceforge.phpdt.internal.ui.preferences.MembersOrderPreferenceCache;
import net.sourceforge.phpdt.internal.ui.viewsupport.AppearanceAwareLabelProvider;
import net.sourceforge.phpdt.internal.ui.viewsupport.DecoratingJavaLabelProvider;
import net.sourceforge.phpdt.internal.ui.viewsupport.JavaElementLabels;
import net.sourceforge.phpdt.internal.ui.viewsupport.StatusBarUpdater;
import net.sourceforge.phpdt.ui.JavaElementSorter;
import net.sourceforge.phpdt.ui.JavaUI;
import net.sourceforge.phpdt.ui.PreferenceConstants;
import net.sourceforge.phpdt.ui.ProblemsLabelDecorator.ProblemsLabelChangedEvent;
import net.sourceforge.phpdt.ui.actions.CustomFiltersActionGroup;
import net.sourceforge.phpdt.ui.actions.GenerateActionGroup;
import net.sourceforge.phpdt.ui.actions.MemberFilterActionGroup;
import net.sourceforge.phpdt.ui.actions.PHPdtActionConstants;
import net.sourceforge.phpeclipse.PHPeclipsePlugin;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.model.WorkbenchAdapter;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.texteditor.GotoAnnotationAction;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.texteditor.TextEditorAction;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.navigator.LocalSelectionTransfer;

/**
 * The content outline page of the Java editor. The viewer implements a
 * proprietary update mechanism based on Java model deltas. It does not react on
 * domain changes. It is specified to show the content of ICompilationUnits and
 * IClassFiles. Publishes its context menu under
 * <code>PHPeclipsePlugin.getDefault().getPluginId() + ".outline"</code>.
 */
public class JavaOutlinePage extends Page implements IContentOutlinePage,
		IAdaptable, IPostSelectionProvider {

	static Object[] NO_CHILDREN = new Object[0];

	/**
	 * The element change listener of the java outline viewer.
	 * 
	 * @see IElementChangedListener
	 */
	class ElementChangedListener implements IElementChangedListener {

		public void elementChanged(final ElementChangedEvent e) {

			if (getControl() == null)
				return;

			Display d = getControl().getDisplay();
			if (d != null) {
				d.asyncExec(new Runnable() {
					public void run() {
						ICompilationUnit cu = (ICompilationUnit) fInput;
						IJavaElement base = cu;
						// if (fTopLevelTypeOnly) {
						// base= getMainType(cu);
						// if (base == null) {
						if (fOutlineViewer != null)
							fOutlineViewer.refresh(true);
						return;
						// }
						// }
						// IJavaElementDelta delta= findElement(base,
						// e.getDelta());
						// if (delta != null && fOutlineViewer != null) {
						// fOutlineViewer.reconcile(delta);
						// }
					}
				});
			}
		}

		private boolean isPossibleStructuralChange(IJavaElementDelta cuDelta) {
			if (cuDelta.getKind() != IJavaElementDelta.CHANGED) {
				return true; // add or remove
			}
			int flags = cuDelta.getFlags();
			if ((flags & IJavaElementDelta.F_CHILDREN) != 0) {
				return true;
			}
			return (flags & (IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_FINE_GRAINED)) == IJavaElementDelta.F_CONTENT;
		}

		protected IJavaElementDelta findElement(IJavaElement unit,
				IJavaElementDelta delta) {

			if (delta == null || unit == null)
				return null;

			IJavaElement element = delta.getElement();

			if (unit.equals(element)) {
				if (isPossibleStructuralChange(delta)) {
					return delta;
				}
				return null;
			}

			if (element.getElementType() > IJavaElement.CLASS_FILE)
				return null;

			IJavaElementDelta[] children = delta.getAffectedChildren();
			if (children == null || children.length == 0)
				return null;

			for (int i = 0; i < children.length; i++) {
				IJavaElementDelta d = findElement(unit, children[i]);
				if (d != null)
					return d;
			}

			return null;
		}
	}

	static class NoClassElement extends WorkbenchAdapter implements IAdaptable {
		/*
		 * @see java.lang.Object#toString()
		 */
		public String toString() {
			return PHPEditorMessages
					.getString("JavaOutlinePage.error.NoTopLevelType"); //$NON-NLS-1$
		}

		/*
		 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(Class)
		 */
		public Object getAdapter(Class clas) {
			if (clas == IWorkbenchAdapter.class)
				return this;
			return null;
		}
	}

	/**
	 * Content provider for the children of an ICompilationUnit or an IClassFile
	 * 
	 * @see ITreeContentProvider
	 */
	class ChildrenProvider implements ITreeContentProvider {

		private Object[] NO_CLASS = new Object[] { new NoClassElement() };

		private ElementChangedListener fListener;

		protected boolean matches(IJavaElement element) {
			if (element.getElementType() == IJavaElement.METHOD) {
				String name = element.getElementName();
				return (name != null && name.indexOf('<') >= 0);
			}
			return false;
		}

		protected IJavaElement[] filter(IJavaElement[] children) {
			boolean initializers = false;
			for (int i = 0; i < children.length; i++) {
				if (matches(children[i])) {
					initializers = true;
					break;
				}
			}

			if (!initializers)
				return children;

			Vector v = new Vector();
			for (int i = 0; i < children.length; i++) {
				if (matches(children[i]))
					continue;
				v.addElement(children[i]);
			}

			IJavaElement[] result = new IJavaElement[v.size()];
			v.copyInto(result);
			return result;
		}

		public Object[] getChildren(Object parent) {
			if (parent instanceof IParent) {
				IParent c = (IParent) parent;
				try {
					return filter(c.getChildren());
				} catch (JavaModelException x) {
					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=38341
					// don't log NotExist exceptions as this is a valid case
					// since we might have been posted and the element
					// removed in the meantime.
					if (PHPeclipsePlugin.isDebug() || !x.isDoesNotExist())
						PHPeclipsePlugin.log(x);
				}
			}
			return NO_CHILDREN;
		}

		public Object[] getElements(Object parent) {
			if (fTopLevelTypeOnly) {
				if (parent instanceof ICompilationUnit) {
					try {
						IType type = getMainType((ICompilationUnit) parent);
						return type != null ? type.getChildren() : NO_CLASS;
					} catch (JavaModelException e) {
						PHPeclipsePlugin.log(e);
					}
				}
				// else if (parent instanceof IClassFile) {
				// try {
				// IType type= getMainType((IClassFile) parent);
				// return type != null ? type.getChildren() : NO_CLASS;
				// } catch (JavaModelException e) {
				// PHPeclipsePlugin.log(e);
				// }
				// }
			}
			return getChildren(parent);
		}

		public Object getParent(Object child) {
			if (child instanceof IJavaElement) {
				IJavaElement e = (IJavaElement) child;
				return e.getParent();
			}
			return null;
		}

		public boolean hasChildren(Object parent) {
			if (parent instanceof IParent) {
				IParent c = (IParent) parent;
				try {
					IJavaElement[] children = filter(c.getChildren());
					return (children != null && children.length > 0);
				} catch (JavaModelException x) {
					// https://bugs.eclipse.org/bugs/show_bug.cgi?id=38341
					// don't log NotExist exceptions as this is a valid case
					// since we might have been posted and the element
					// removed in the meantime.
					if (PHPeclipsePlugin.isDebug() || !x.isDoesNotExist())
						PHPeclipsePlugin.log(x);
				}
			}
			return false;
		}

		public boolean isDeleted(Object o) {
			return false;
		}

		public void dispose() {
			if (fListener != null) {
				JavaCore.removeElementChangedListener(fListener);
				fListener = null;
			}
		}

		/*
		 * @see IContentProvider#inputChanged(Viewer, Object, Object)
		 */
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			boolean isCU = (newInput instanceof ICompilationUnit);

			if (isCU && fListener == null) {
				fListener = new ElementChangedListener();
				JavaCore.addElementChangedListener(fListener);
			} else if (!isCU && fListener != null) {
				JavaCore.removeElementChangedListener(fListener);
				fListener = null;
			}
		}
	}

	class JavaOutlineViewer extends TreeViewer {

		/**
		 * Indicates an item which has been reused. At the point of its reuse it
		 * has been expanded. This field is used to communicate between
		 * <code>internalExpandToLevel</code> and <code>reuseTreeItem</code>.
		 */
		private Item fReusedExpandedItem;

		private boolean fReorderedMembers;

		private boolean fForceFireSelectionChanged;

		public JavaOutlineViewer(Tree tree) {
			super(tree);
			setAutoExpandLevel(ALL_LEVELS);
			setUseHashlookup(true);
		}

		/**
		 * Investigates the given element change event and if affected
		 * incrementally updates the Java outline.
		 * 
		 * @param delta
		 *            the Java element delta used to reconcile the Java outline
		 */
		public void reconcile(IJavaElementDelta delta) {
			fReorderedMembers = false;
			fForceFireSelectionChanged = false;
			if (getSorter() == null) {
				if (fTopLevelTypeOnly && delta.getElement() instanceof IType
						&& (delta.getKind() & IJavaElementDelta.ADDED) != 0) {
					refresh(true);

				} else {
					Widget w = findItem(fInput);
					if (w != null && !w.isDisposed())
						update(w, delta);
					if (fForceFireSelectionChanged)
						fireSelectionChanged(new SelectionChangedEvent(
								getSite().getSelectionProvider(), this
										.getSelection()));
					if (fReorderedMembers) {
						refresh(false);
						fReorderedMembers = false;
					}
				}
			} else {
				// just for now
				refresh(true);
			}
		}

		/*
		 * @see TreeViewer#internalExpandToLevel
		 */
		protected void internalExpandToLevel(Widget node, int level) {
			if (node instanceof Item) {
				Item i = (Item) node;
				if (i.getData() instanceof IJavaElement) {
					IJavaElement je = (IJavaElement) i.getData();
					if (je.getElementType() == IJavaElement.IMPORT_CONTAINER
							|| isInnerType(je)) {
						if (i != fReusedExpandedItem) {
							setExpanded(i, false);
							return;
						}
					}
				}
			}
			super.internalExpandToLevel(node, level);
		}

		protected void reuseTreeItem(Item item, Object element) {

			// remove children
			Item[] c = getChildren(item);
			if (c != null && c.length > 0) {

				if (getExpanded(item))
					fReusedExpandedItem = item;

				for (int k = 0; k < c.length; k++) {
					if (c[k].getData() != null)
						disassociate(c[k]);
					c[k].dispose();
				}
			}

			updateItem(item, element);
			updatePlus(item, element);
			internalExpandToLevel(item, ALL_LEVELS);

			fReusedExpandedItem = null;
			fForceFireSelectionChanged = true;
		}

		protected boolean mustUpdateParent(IJavaElementDelta delta,
				IJavaElement element) {
			if (element instanceof IMethod) {
				if ((delta.getKind() & IJavaElementDelta.ADDED) != 0) {
					try {
						return ((IMethod) element).isMainMethod();
					} catch (JavaModelException e) {
						PHPeclipsePlugin.log(e.getStatus());
					}
				}
				return "main".equals(element.getElementName()); //$NON-NLS-1$
			}
			return false;
		}

		/*
		 * @see org.eclipse.jface.viewers.AbstractTreeViewer#isExpandable(java.lang.Object)
		 */
		public boolean isExpandable(Object element) {
			if (hasFilters()) {
				return getFilteredChildren(element).length > 0;
			}
			return super.isExpandable(element);
		}

		protected ISourceRange getSourceRange(IJavaElement element)
				throws JavaModelException {
			if (element instanceof ISourceReference)
				return ((ISourceReference) element).getSourceRange();
			if (element instanceof IMember)// && !(element instanceof
											// IInitializer))
				return ((IMember) element).getNameRange();
			return null;
		}

		protected boolean overlaps(ISourceRange range, int start, int end) {
			return start <= (range.getOffset() + range.getLength() - 1)
					&& range.getOffset() <= end;
		}

		protected boolean filtered(IJavaElement parent, IJavaElement child) {

			Object[] result = new Object[] { child };
			ViewerFilter[] filters = getFilters();
			for (int i = 0; i < filters.length; i++) {
				result = filters[i].filter(this, parent, result);
				if (result.length == 0)
					return true;
			}

			return false;
		}

		protected void update(Widget w, IJavaElementDelta delta) {

			Item item;

			IJavaElement parent = delta.getElement();
			IJavaElementDelta[] affected = delta.getAffectedChildren();
			Item[] children = getChildren(w);

			boolean doUpdateParent = false;
			boolean doUpdateParentsPlus = false;

			Vector deletions = new Vector();
			Vector additions = new Vector();

			for (int i = 0; i < affected.length; i++) {
				IJavaElementDelta affectedDelta = affected[i];
				IJavaElement affectedElement = affectedDelta.getElement();
				int status = affected[i].getKind();

				// find tree item with affected element
				int j;
				for (j = 0; j < children.length; j++)
					if (affectedElement.equals(children[j].getData()))
						break;

				if (j == children.length) {
					// remove from collapsed parent
					if ((status & IJavaElementDelta.REMOVED) != 0) {
						doUpdateParentsPlus = true;
						continue;
					}
					// addition
					if ((status & IJavaElementDelta.CHANGED) != 0
							&& (affectedDelta.getFlags() & IJavaElementDelta.F_MODIFIERS) != 0
							&& !filtered(parent, affectedElement)) {
						additions.addElement(affectedDelta);
					}
					continue;
				}

				item = children[j];

				// removed
				if ((status & IJavaElementDelta.REMOVED) != 0) {
					deletions.addElement(item);
					doUpdateParent = doUpdateParent
							|| mustUpdateParent(affectedDelta, affectedElement);

					// changed
				} else if ((status & IJavaElementDelta.CHANGED) != 0) {
					int change = affectedDelta.getFlags();
					doUpdateParent = doUpdateParent
							|| mustUpdateParent(affectedDelta, affectedElement);

					if ((change & IJavaElementDelta.F_MODIFIERS) != 0) {
						if (filtered(parent, affectedElement))
							deletions.addElement(item);
						else
							updateItem(item, affectedElement);
					}

					if ((change & IJavaElementDelta.F_CONTENT) != 0)
						updateItem(item, affectedElement);

					if ((change & IJavaElementDelta.F_CHILDREN) != 0)
						update(item, affectedDelta);

					if ((change & IJavaElementDelta.F_REORDER) != 0)
						fReorderedMembers = true;
				}
			}

			// find all elements to add
			IJavaElementDelta[] add = delta.getAddedChildren();
			if (additions.size() > 0) {
				IJavaElementDelta[] tmp = new IJavaElementDelta[add.length
						+ additions.size()];
				System.arraycopy(add, 0, tmp, 0, add.length);
				for (int i = 0; i < additions.size(); i++)
					tmp[i + add.length] = (IJavaElementDelta) additions
							.elementAt(i);
				add = tmp;
			}

			// add at the right position
			go2: for (int i = 0; i < add.length; i++) {

				try {

					IJavaElement e = add[i].getElement();
					if (filtered(parent, e))
						continue go2;

					doUpdateParent = doUpdateParent
							|| mustUpdateParent(add[i], e);
					ISourceRange rng = getSourceRange(e);
					int start = rng.getOffset();
					int end = start + rng.getLength() - 1;
					int nameOffset = Integer.MAX_VALUE;
					if (e instanceof IField) {
						ISourceRange nameRange = ((IField) e).getNameRange();
						if (nameRange != null)
							nameOffset = nameRange.getOffset();
					}

					Item last = null;
					item = null;
					children = getChildren(w);

					for (int j = 0; j < children.length; j++) {
						item = children[j];
						IJavaElement r = (IJavaElement) item.getData();

						if (r == null) {
							// parent node collapsed and not be opened before ->
							// do nothing
							continue go2;
						}

						try {
							rng = getSourceRange(r);

							// multi-field declarations always start at
							// the same offset. They also have the same
							// end offset if the field sequence is terminated
							// with a semicolon. If not, the source range
							// ends behind the identifier / initializer
							// see
							// https://bugs.eclipse.org/bugs/show_bug.cgi?id=51851
							boolean multiFieldDeclaration = r.getElementType() == IJavaElement.FIELD
									&& e.getElementType() == IJavaElement.FIELD
									&& rng.getOffset() == start;

							// elements are inserted by occurrence
							// however, multi-field declarations have
							// equal source ranges offsets, therefore we
							// compare name-range offsets.
							boolean multiFieldOrderBefore = false;
							if (multiFieldDeclaration) {
								if (r instanceof IField) {
									ISourceRange nameRange = ((IField) r)
											.getNameRange();
									if (nameRange != null) {
										if (nameRange.getOffset() > nameOffset)
											multiFieldOrderBefore = true;
									}
								}
							}

							if (!multiFieldDeclaration
									&& overlaps(rng, start, end)) {

								// be tolerant if the delta is not correct, or
								// if
								// the tree has been updated other than by a
								// delta
								reuseTreeItem(item, e);
								continue go2;

							} else if (multiFieldOrderBefore
									|| rng.getOffset() > start) {

								if (last != null && deletions.contains(last)) {
									// reuse item
									deletions.removeElement(last);
									reuseTreeItem(last, e);
								} else {
									// nothing to reuse
									createTreeItem(w, e, j);
								}
								continue go2;
							}

						} catch (JavaModelException x) {
							// stumbled over deleted element
						}

						last = item;
					}

					// add at the end of the list
					if (last != null && deletions.contains(last)) {
						// reuse item
						deletions.removeElement(last);
						reuseTreeItem(last, e);
					} else {
						// nothing to reuse
						createTreeItem(w, e, -1);
					}

				} catch (JavaModelException x) {
					// the element to be added is not present -> don't add it
				}
			}

			// remove items which haven't been reused
			Enumeration e = deletions.elements();
			while (e.hasMoreElements()) {
				item = (Item) e.nextElement();
				disassociate(item);
				item.dispose();
			}

			if (doUpdateParent)
				updateItem(w, delta.getElement());
			if (!doUpdateParent && doUpdateParentsPlus && w instanceof Item)
				updatePlus((Item) w, delta.getElement());
		}

		/*
		 * @see ContentViewer#handleLabelProviderChanged(LabelProviderChangedEvent)
		 */
		protected void handleLabelProviderChanged(
				LabelProviderChangedEvent event) {
			Object input = getInput();
			if (event instanceof ProblemsLabelChangedEvent) {
				ProblemsLabelChangedEvent e = (ProblemsLabelChangedEvent) event;
				if (e.isMarkerChange() && input instanceof ICompilationUnit) {
					return; // marker changes can be ignored
				}
			}
			// look if the underlying resource changed
			Object[] changed = event.getElements();
			if (changed != null) {
				IResource resource = getUnderlyingResource();
				if (resource != null) {
					for (int i = 0; i < changed.length; i++) {
						if (changed[i] != null && changed[i].equals(resource)) {
							// change event to a full refresh
							event = new LabelProviderChangedEvent(
									(IBaseLabelProvider) event.getSource());
							break;
						}
					}
				}
			}
			super.handleLabelProviderChanged(event);
		}

		private IResource getUnderlyingResource() {
			Object input = getInput();
			if (input instanceof ICompilationUnit) {
				ICompilationUnit cu = (ICompilationUnit) input;
				cu = JavaModelUtil.toOriginal(cu);
				return cu.getResource();
			}
			// else if (input instanceof IClassFile) {
			// return ((IClassFile) input).getResource();
			// }
			return null;
		}

	}

	class LexicalSortingAction extends Action {

		private JavaElementSorter fSorter = new JavaElementSorter();

		public LexicalSortingAction() {
			super();
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
					IJavaHelpContextIds.LEXICAL_SORTING_OUTLINE_ACTION);
			setText(PHPEditorMessages.getString("JavaOutlinePage.Sort.label")); //$NON-NLS-1$
			PHPUiImages.setLocalImageDescriptors(this, "alphab_sort_co.gif"); //$NON-NLS-1$
			setToolTipText(PHPEditorMessages
					.getString("JavaOutlinePage.Sort.tooltip")); //$NON-NLS-1$
			setDescription(PHPEditorMessages
					.getString("JavaOutlinePage.Sort.description")); //$NON-NLS-1$

			boolean checked = PHPeclipsePlugin.getDefault()
					.getPreferenceStore().getBoolean(
							"LexicalSortingAction.isChecked"); //$NON-NLS-1$
			valueChanged(checked, false);
		}

		public void run() {
			valueChanged(isChecked(), true);
		}

		private void valueChanged(final boolean on, boolean store) {
			setChecked(on);
			BusyIndicator.showWhile(fOutlineViewer.getControl().getDisplay(),
					new Runnable() {
						public void run() {
							fOutlineViewer.setSorter(on ? fSorter : null);
						}
					});

			if (store)
				PHPeclipsePlugin.getDefault().getPreferenceStore().setValue(
						"LexicalSortingAction.isChecked", on); //$NON-NLS-1$
		}
	}

	class ClassOnlyAction extends Action {

		public ClassOnlyAction() {
			super();
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
					IJavaHelpContextIds.GO_INTO_TOP_LEVEL_TYPE_ACTION);
			setText(PHPEditorMessages
					.getString("JavaOutlinePage.GoIntoTopLevelType.label")); //$NON-NLS-1$
			setToolTipText(PHPEditorMessages
					.getString("JavaOutlinePage.GoIntoTopLevelType.tooltip")); //$NON-NLS-1$
			setDescription(PHPEditorMessages
					.getString("JavaOutlinePage.GoIntoTopLevelType.description")); //$NON-NLS-1$
			PHPUiImages.setLocalImageDescriptors(this,
					"gointo_toplevel_type.gif"); //$NON-NLS-1$

			IPreferenceStore preferenceStore = PHPeclipsePlugin.getDefault()
					.getPreferenceStore();
			boolean showclass = preferenceStore
					.getBoolean("GoIntoTopLevelTypeAction.isChecked"); //$NON-NLS-1$
			setTopLevelTypeOnly(showclass);
		}

		/*
		 * @see org.eclipse.jface.action.Action#run()
		 */
		public void run() {
			setTopLevelTypeOnly(!fTopLevelTypeOnly);
		}

		private void setTopLevelTypeOnly(boolean show) {
			fTopLevelTypeOnly = show;
			setChecked(show);
			fOutlineViewer.refresh(false);

			IPreferenceStore preferenceStore = PHPeclipsePlugin.getDefault()
					.getPreferenceStore();
			preferenceStore
					.setValue("GoIntoTopLevelTypeAction.isChecked", show); //$NON-NLS-1$
		}
	}

	/**
	 * This action toggles whether this Java Outline page links its selection to
	 * the active editor.
	 * 
	 * @since 3.0
	 */
	public class ToggleLinkingAction extends AbstractToggleLinkingAction {

		JavaOutlinePage fJavaOutlinePage;

		/**
		 * Constructs a new action.
		 * 
		 * @param outlinePage
		 *            the Java outline page
		 */
		public ToggleLinkingAction(JavaOutlinePage outlinePage) {
			boolean isLinkingEnabled = PreferenceConstants
					.getPreferenceStore()
					.getBoolean(
							PreferenceConstants.EDITOR_SYNC_OUTLINE_ON_CURSOR_MOVE);
			setChecked(isLinkingEnabled);
			fJavaOutlinePage = outlinePage;
		}

		/**
		 * Runs the action.
		 */
		public void run() {
			PreferenceConstants.getPreferenceStore().setValue(
					PreferenceConstants.EDITOR_SYNC_OUTLINE_ON_CURSOR_MOVE,
					isChecked());
			if (isChecked() && fEditor != null)
				fEditor.synchronizeOutlinePage(fEditor
						.computeHighlightRangeSourceReference(), false);
		}

	}

	/** A flag to show contents of top level type only */
	private boolean fTopLevelTypeOnly;

	private IJavaElement fInput;

	private String fContextMenuID;

	private Menu fMenu;

	private JavaOutlineViewer fOutlineViewer;

	private PHPEditor fEditor;

	private MemberFilterActionGroup fMemberFilterActionGroup;

	private ListenerList fSelectionChangedListeners = new ListenerList();

	private ListenerList fPostSelectionChangedListeners = new ListenerList();

	private Hashtable fActions = new Hashtable();

	private TogglePresentationAction fTogglePresentation;

	private GotoAnnotationAction fPreviousAnnotation;

	private GotoAnnotationAction fNextAnnotation;

	private TextEditorAction fShowJavadoc;

	private IAction fUndo;

	private IAction fRedo;

	private ToggleLinkingAction fToggleLinkingAction;

	private CompositeActionGroup fActionGroups;

	private IPropertyChangeListener fPropertyChangeListener;

	/**
	 * Custom filter action group.
	 * 
	 * @since 3.0
	 */
	private CustomFiltersActionGroup fCustomFiltersActionGroup;

	public JavaOutlinePage(String contextMenuID, PHPEditor editor) {
		super();

		Assert.isNotNull(editor);

		fContextMenuID = contextMenuID;
		fEditor = editor;
		fTogglePresentation = new TogglePresentationAction();
		ResourceBundle bundle = PHPEditorMessages.getResourceBundle();
		fPreviousAnnotation = new GotoAnnotationAction(bundle,
				"PreviousAnnotation.", null, false); //$NON-NLS-1$
		fNextAnnotation = new GotoAnnotationAction(bundle,
				"NextAnnotation.", null, true); //$NON-NLS-1$
		fShowJavadoc = (TextEditorAction) fEditor.getAction("ShowJavaDoc"); //$NON-NLS-1$
		fUndo = fEditor.getAction(ITextEditorActionConstants.UNDO);
		fRedo = fEditor.getAction(ITextEditorActionConstants.REDO);

		fTogglePresentation.setEditor(editor);
		fPreviousAnnotation.setEditor(editor);
		fNextAnnotation.setEditor(editor);

		fPropertyChangeListener = new IPropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent event) {
				doPropertyChange(event);
			}
		};
		PHPeclipsePlugin.getDefault().getPreferenceStore()
				.addPropertyChangeListener(fPropertyChangeListener);
	}

	/**
	 * Returns the primary type of a compilation unit (has the same name as the
	 * compilation unit).
	 * 
	 * @param compilationUnit
	 *            the compilation unit
	 * @return returns the primary type of the compilation unit, or
	 *         <code>null</code> if is does not have one
	 */
	protected IType getMainType(ICompilationUnit compilationUnit) {

		if (compilationUnit == null)
			return null;

		String name = compilationUnit.getElementName();
		int index = name.indexOf('.');
		if (index != -1)
			name = name.substring(0, index);
		IType type = compilationUnit.getType(name);
		return type.exists() ? type : null;
	}

	/**
	 * Returns the primary type of a class file.
	 * 
	 * @param classFile
	 *            the class file
	 * @return returns the primary type of the class file, or <code>null</code>
	 *         if is does not have one
	 */
	// protected IType getMainType(IClassFile classFile) {
	// try {
	// IType type= classFile.getType();
	// return type != null && type.exists() ? type : null;
	// } catch (JavaModelException e) {
	// return null;
	// }
	// }
	/*
	 * (non-Javadoc) Method declared on Page
	 */
	public void init(IPageSite pageSite) {
		super.init(pageSite);
	}

	private void doPropertyChange(PropertyChangeEvent event) {
		if (fOutlineViewer != null) {
			if (MembersOrderPreferenceCache.isMemberOrderProperty(event
					.getProperty())) {
				fOutlineViewer.refresh(false);
			}
		}
	}

	/*
	 * @see ISelectionProvider#addSelectionChangedListener(ISelectionChangedListener)
	 */
	public void addSelectionChangedListener(ISelectionChangedListener listener) {
		if (fOutlineViewer != null)
			fOutlineViewer.addSelectionChangedListener(listener);
		else
			fSelectionChangedListeners.add(listener);
	}

	/*
	 * @see ISelectionProvider#removeSelectionChangedListener(ISelectionChangedListener)
	 */
	public void removeSelectionChangedListener(
			ISelectionChangedListener listener) {
		if (fOutlineViewer != null)
			fOutlineViewer.removeSelectionChangedListener(listener);
		else
			fSelectionChangedListeners.remove(listener);
	}

	/*
	 * @see ISelectionProvider#setSelection(ISelection)
	 */
	public void setSelection(ISelection selection) {
		if (fOutlineViewer != null)
			fOutlineViewer.setSelection(selection);
	}

	/*
	 * @see ISelectionProvider#getSelection()
	 */
	public ISelection getSelection() {
		if (fOutlineViewer == null)
			return StructuredSelection.EMPTY;
		return fOutlineViewer.getSelection();
	}

	/*
	 * @see org.eclipse.jface.text.IPostSelectionProvider#addPostSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 */
	public void addPostSelectionChangedListener(
			ISelectionChangedListener listener) {
		if (fOutlineViewer != null)
			fOutlineViewer.addPostSelectionChangedListener(listener);
		else
			fPostSelectionChangedListeners.add(listener);
	}

	/*
	 * @see org.eclipse.jface.text.IPostSelectionProvider#removePostSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 */
	public void removePostSelectionChangedListener(
			ISelectionChangedListener listener) {
		if (fOutlineViewer != null)
			fOutlineViewer.removePostSelectionChangedListener(listener);
		else
			fPostSelectionChangedListeners.remove(listener);
	}

	private void registerToolbarActions(IActionBars actionBars) {

		IToolBarManager toolBarManager = actionBars.getToolBarManager();
		if (toolBarManager != null) {
			toolBarManager.add(new LexicalSortingAction());

			fMemberFilterActionGroup = new MemberFilterActionGroup(
					fOutlineViewer,
					"net.sourceforge.phpeclipse.JavaOutlinePage"); //$NON-NLS-1$
			fMemberFilterActionGroup.contributeToToolBar(toolBarManager);

			fCustomFiltersActionGroup.fillActionBars(actionBars);

			IMenuManager menu = actionBars.getMenuManager();
			menu.add(new Separator("EndFilterGroup")); //$NON-NLS-1$

			fToggleLinkingAction = new ToggleLinkingAction(this);
			menu.add(new ClassOnlyAction());
			menu.add(fToggleLinkingAction);
		}
	}

	/*
	 * @see IPage#createControl
	 */
	public void createControl(Composite parent) {

		Tree tree = new Tree(parent, SWT.MULTI);

		AppearanceAwareLabelProvider lprovider = new AppearanceAwareLabelProvider(
				AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS
						| JavaElementLabels.F_APP_TYPE_SIGNATURE,
				AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS);

		fOutlineViewer = new JavaOutlineViewer(tree);
		initDragAndDrop();
		fOutlineViewer.setContentProvider(new ChildrenProvider());
		fOutlineViewer.setLabelProvider(new DecoratingJavaLabelProvider(
				lprovider));

		Object[] listeners = fSelectionChangedListeners.getListeners();
		for (int i = 0; i < listeners.length; i++) {
			fSelectionChangedListeners.remove(listeners[i]);
			fOutlineViewer
					.addSelectionChangedListener((ISelectionChangedListener) listeners[i]);
		}

		listeners = fPostSelectionChangedListeners.getListeners();
		for (int i = 0; i < listeners.length; i++) {
			fPostSelectionChangedListeners.remove(listeners[i]);
			fOutlineViewer
					.addPostSelectionChangedListener((ISelectionChangedListener) listeners[i]);
		}

		MenuManager manager = new MenuManager(fContextMenuID, fContextMenuID);
		manager.setRemoveAllWhenShown(true);
		manager.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager m) {
				contextMenuAboutToShow(m);
			}
		});
		fMenu = manager.createContextMenu(tree);
		tree.setMenu(fMenu);

		IPageSite site = getSite();
		site
				.registerContextMenu(PHPeclipsePlugin.getPluginId()
						+ ".outline", manager, fOutlineViewer); //$NON-NLS-1$
		site.setSelectionProvider(fOutlineViewer);

		// we must create the groups after we have set the selection provider to
		// the site
		fActionGroups = new CompositeActionGroup(new ActionGroup[] {
		// new OpenViewActionGroup(this),
				// new CCPActionGroup(this),
				new GenerateActionGroup(this) });
		// new RefactorActionGroup(this),
		// new JavaSearchActionGroup(this)});

		// register global actions
		IActionBars bars = site.getActionBars();

		bars.setGlobalActionHandler(ITextEditorActionConstants.UNDO, fUndo);
		bars.setGlobalActionHandler(ITextEditorActionConstants.REDO, fRedo);
		bars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(),
				fPreviousAnnotation);
		bars
				.setGlobalActionHandler(ActionFactory.NEXT.getId(),
						fNextAnnotation);
		bars.setGlobalActionHandler(PHPdtActionConstants.SHOW_JAVA_DOC,
				fShowJavadoc);
		bars
				.setGlobalActionHandler(
						ITextEditorActionDefinitionIds.TOGGLE_SHOW_SELECTED_ELEMENT_ONLY,
						fTogglePresentation);
		bars.setGlobalActionHandler(
				ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION,
				fNextAnnotation);
		bars.setGlobalActionHandler(
				ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION,
				fPreviousAnnotation);

		fActionGroups.fillActionBars(bars);

		IStatusLineManager statusLineManager = bars.getStatusLineManager();
		if (statusLineManager != null) {
			StatusBarUpdater updater = new StatusBarUpdater(statusLineManager);
			fOutlineViewer.addPostSelectionChangedListener(updater);
		}
		// Custom filter group
		fCustomFiltersActionGroup = new CustomFiltersActionGroup(
				"net.sourceforge.phpdt.ui.JavaOutlinePage", fOutlineViewer); //$NON-NLS-1$

		registerToolbarActions(bars);

		fOutlineViewer.setInput(fInput);
	}

	public void dispose() {

		if (fEditor == null)
			return;

		if (fMemberFilterActionGroup != null) {
			fMemberFilterActionGroup.dispose();
			fMemberFilterActionGroup = null;
		}

		if (fCustomFiltersActionGroup != null) {
			fCustomFiltersActionGroup.dispose();
			fCustomFiltersActionGroup = null;
		}

		fEditor.outlinePageClosed();
		fEditor = null;

		fSelectionChangedListeners.clear();
		fSelectionChangedListeners = null;

		fPostSelectionChangedListeners.clear();
		fPostSelectionChangedListeners = null;

		if (fPropertyChangeListener != null) {
			PHPeclipsePlugin.getDefault().getPreferenceStore()
					.removePropertyChangeListener(fPropertyChangeListener);
			fPropertyChangeListener = null;
		}

		if (fMenu != null && !fMenu.isDisposed()) {
			fMenu.dispose();
			fMenu = null;
		}

		if (fActionGroups != null)
			fActionGroups.dispose();

		fTogglePresentation.setEditor(null);
		fPreviousAnnotation.setEditor(null);
		fNextAnnotation.setEditor(null);

		fOutlineViewer = null;

		super.dispose();
	}

	public Control getControl() {
		if (fOutlineViewer != null)
			return fOutlineViewer.getControl();
		return null;
	}

	public void setInput(IJavaElement inputElement) {
		fInput = inputElement;
		if (fOutlineViewer != null)
			fOutlineViewer.setInput(fInput);
	}

	public void select(ISourceReference reference) {
		if (fOutlineViewer != null) {

			ISelection s = fOutlineViewer.getSelection();
			if (s instanceof IStructuredSelection) {
				IStructuredSelection ss = (IStructuredSelection) s;
				List elements = ss.toList();
				if (!elements.contains(reference)) {
					s = (reference == null ? StructuredSelection.EMPTY
							: new StructuredSelection(reference));
					fOutlineViewer.setSelection(s, true);
				}
			}
		}
	}

	public void setAction(String actionID, IAction action) {
		Assert.isNotNull(actionID);
		if (action == null)
			fActions.remove(actionID);
		else
			fActions.put(actionID, action);
	}

	public IAction getAction(String actionID) {
		Assert.isNotNull(actionID);
		return (IAction) fActions.get(actionID);
	}

	/*
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class key) {
		if (key == IShowInSource.class) {
			return getShowInSource();
		}
		if (key == IShowInTargetList.class) {
			return new IShowInTargetList() {
				public String[] getShowInTargetIds() {
					return new String[] { JavaUI.ID_PACKAGES };
				}

			};
		}
		if (key == IShowInTarget.class) {
			return getShowInTarget();
		}

		return null;
	}

	/**
	 * Convenience method to add the action installed under the given actionID
	 * to the specified group of the menu.
	 * 
	 * @param menu
	 *            the menu manager
	 * @param group
	 *            the group to which to add the action
	 * @param actionID
	 *            the ID of the new action
	 */
	protected void addAction(IMenuManager menu, String group, String actionID) {
		IAction action = getAction(actionID);
		if (action != null) {
			if (action instanceof IUpdate)
				((IUpdate) action).update();

			if (action.isEnabled()) {
				IMenuManager subMenu = menu.findMenuUsingPath(group);
				if (subMenu != null)
					subMenu.add(action);
				else
					menu.appendToGroup(group, action);
			}
		}
	}

	protected void contextMenuAboutToShow(IMenuManager menu) {

		PHPeclipsePlugin.createStandardGroups(menu);

		IStructuredSelection selection = (IStructuredSelection) getSelection();
		fActionGroups.setContext(new ActionContext(selection));
		fActionGroups.fillContextMenu(menu);
	}

	/*
	 * @see Page#setFocus()
	 */
	public void setFocus() {
		if (fOutlineViewer != null)
			fOutlineViewer.getControl().setFocus();
	}

	/**
	 * Checks whether a given Java element is an inner type.
	 * 
	 * @param element
	 *            the java element
	 * @return <code>true</code> iff the given element is an inner type
	 */
	private boolean isInnerType(IJavaElement element) {

		if (element != null && element.getElementType() == IJavaElement.TYPE) {
			IType type = (IType) element;
			try {
				return type.isMember();
			} catch (JavaModelException e) {
				IJavaElement parent = type.getParent();
				if (parent != null) {
					int parentElementType = parent.getElementType();
					return (parentElementType != IJavaElement.COMPILATION_UNIT && parentElementType != IJavaElement.CLASS_FILE);
				}
			}
		}

		return false;
	}

	/**
	 * Returns the <code>IShowInSource</code> for this view.
	 * 
	 * @return the {@link IShowInSource}
	 */
	protected IShowInSource getShowInSource() {
		return new IShowInSource() {
			public ShowInContext getShowInContext() {
				return new ShowInContext(null, getSite().getSelectionProvider()
						.getSelection());
			}
		};
	}

	/**
	 * Returns the <code>IShowInTarget</code> for this view.
	 * 
	 * @return the {@link IShowInTarget}
	 */
	protected IShowInTarget getShowInTarget() {
		return new IShowInTarget() {
			public boolean show(ShowInContext context) {
				ISelection sel = context.getSelection();
				if (sel instanceof ITextSelection) {
					ITextSelection tsel = (ITextSelection) sel;
					int offset = tsel.getOffset();
					IJavaElement element = fEditor.getElementAt(offset);
					if (element != null) {
						setSelection(new StructuredSelection(element));
						return true;
					}
				}
				return false;
			}
		};
	}

	private void initDragAndDrop() {
		int ops = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK;
		Transfer[] transfers = new Transfer[] { LocalSelectionTransfer
				.getInstance() };

		// Drop Adapter
		// TransferDropTargetListener[] dropListeners= new
		// TransferDropTargetListener[] {
		// new SelectionTransferDropAdapter(fOutlineViewer)
		// };
		// fOutlineViewer.addDropSupport(ops | DND.DROP_DEFAULT, transfers, new
		// DelegatingDropAdapter(dropListeners));

		// Drag Adapter
		// TransferDragSourceListener[] dragListeners= new
		// TransferDragSourceListener[] {
		// new SelectionTransferDragAdapter(fOutlineViewer)
		// };
		// fOutlineViewer.addDragSupport(ops, transfers, new
		// JdtViewerDragAdapter(fOutlineViewer, dragListeners));
	}
}
