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

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

import net.sourceforge.phpdt.core.ICompilationUnit;
import net.sourceforge.phpdt.core.IJavaElement;
import net.sourceforge.phpdt.core.IJavaElementDelta;
import net.sourceforge.phpdt.core.IJavaModel;
import net.sourceforge.phpdt.core.IJavaProject;
import net.sourceforge.phpdt.core.IPackageFragment;
import net.sourceforge.phpdt.core.IPackageFragmentRoot;
import net.sourceforge.phpdt.core.IParent;
import net.sourceforge.phpdt.core.ISourceReference;
import net.sourceforge.phpdt.core.JavaCore;
import net.sourceforge.phpdt.core.JavaModelException;
import net.sourceforge.phpdt.internal.corext.util.JavaModelUtil;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;

/**
 * A base content provider for Java elements. It provides access to the Java
 * element hierarchy without listening to changes in the Java model. If updating
 * the presentation on Java model change is required than clients have to
 * subclass, listen to Java model changes and have to update the UI using
 * corresponding methods provided by the JFace viewers or their own UI
 * presentation.
 * <p>
 * The following Java element hierarchy is surfaced by this content provider:
 * <p>
 * 
 * <pre>
 *  Java model (
 * <code>
 * IJavaModel
 * </code>
 * )
 *  Java project (
 * <code>
 * IJavaProject
 * </code>
 * )
 *  package fragment root (
 * <code>
 * IPackageFragmentRoot
 * </code>
 * )
 *  package fragment (
 * <code>
 * IPackageFragment
 * </code>
 * )
 *  compilation unit (
 * <code>
 * ICompilationUnit
 * </code>
 * )
 *  binary class file (
 * <code>
 * IClassFile
 * </code>
 * )
 * </pre>
 * 
 * </p>
 * <p>
 * Note that when the entire Java project is declared to be package fragment
 * root, the corresponding package fragment root element that normally appears
 * between the Java project and the package fragments is automatically filtered
 * out.
 * </p>
 * This content provider can optionally return working copy elements for members
 * below compilation units. If enabled, working copy members are returned for
 * those compilation units in the Java element hierarchy for which a shared
 * working copy exists in JDT core.
 * 
 * @see net.sourceforge.phpdt.ui.IWorkingCopyProvider
 * @see JavaCore#getSharedWorkingCopies(net.sourceforge.phpdt.core.IBufferFactory)
 * 
 * @since 2.0
 */
public class StandardJavaElementContentProvider implements
		ITreeContentProvider, IWorkingCopyProvider {

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

	protected boolean fProvideMembers = false;

	protected boolean fProvideWorkingCopy = false;

	/**
	 * Creates a new content provider. The content provider does not provide
	 * members of compilation units or class files and it does not provide
	 * working copy elements.
	 */
	public StandardJavaElementContentProvider() {
	}

	/**
	 * Creates a new <code>StandardJavaElementContentProvider</code>.
	 * 
	 * @param provideMembers
	 *            if <code>true</code> members below compilation units and
	 *            class files are provided.
	 * @param provideWorkingCopy
	 *            if <code>true</code> the element provider provides working
	 *            copies members of compilation units which have an associated
	 *            working copy in JDT core. Otherwise only original elements are
	 *            provided.
	 */
	public StandardJavaElementContentProvider(boolean provideMembers,
			boolean provideWorkingCopy) {
		fProvideMembers = provideMembers;
		fProvideWorkingCopy = provideWorkingCopy;
	}

	/**
	 * Returns whether members are provided when asking for a compilation units
	 * or class file for its children.
	 * 
	 * @return <code>true</code> if the content provider provides members;
	 *         otherwise <code>false</code> is returned
	 */
	public boolean getProvideMembers() {
		return fProvideMembers;
	}

	/**
	 * Sets whether the content provider is supposed to return members when
	 * asking a compilation unit or class file for its children.
	 * 
	 * @param b
	 *            if <code>true</code> then members are provided. If
	 *            <code>false</code> compilation units and class files are the
	 *            leaves provided by this content provider.
	 */
	public void setProvideMembers(boolean b) {
		fProvideMembers = b;
	}

	/**
	 * Returns whether the provided members are from a working copy or the
	 * original compilation unit.
	 * 
	 * @return <code>true</code> if the content provider provides working copy
	 *         members; otherwise <code>false</code> is returned
	 * 
	 * @see #setProvideWorkingCopy(boolean)
	 */
	public boolean getProvideWorkingCopy() {
		return fProvideWorkingCopy;
	}

	/**
	 * Sets whether the members are provided from a shared working copy that
	 * exists for a original compilation unit in the Java element hierarchy.
	 * 
	 * @param b
	 *            if <code>true</code> members are provided from a working
	 *            copy if one exists in JDT core. If <code>false</code> the
	 *            provider always returns original elements.
	 */
	public void setProvideWorkingCopy(boolean b) {
		fProvideWorkingCopy = b;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see IWorkingCopyProvider#providesWorkingCopies()
	 */
	public boolean providesWorkingCopies() {
		return fProvideWorkingCopy;
	}

	/*
	 * (non-Javadoc) Method declared on IStructuredContentProvider.
	 */
	public Object[] getElements(Object parent) {
		return getChildren(parent);
	}

	/*
	 * (non-Javadoc) Method declared on IContentProvider.
	 */
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
	}

	/*
	 * (non-Javadoc) Method declared on IContentProvider.
	 */
	public void dispose() {
	}

	/*
	 * (non-Javadoc) Method declared on ITreeContentProvider.
	 */
	public Object[] getChildren(Object element) {
		if (!exists(element))
			return NO_CHILDREN;

		try {
			if (element instanceof IJavaModel)
				return getJavaProjects((IJavaModel) element);

			// if (element instanceof IJavaProject)
			// return getPackageFragmentRoots((IJavaProject)element);
			//			
			if (element instanceof IPackageFragmentRoot)
				return getPackageFragments((IPackageFragmentRoot) element);

			// if (element instanceof IPackageFragment)
			// return getPackageContents((IPackageFragment)element);

			if (element instanceof IFolder)
				return getResources((IFolder) element);

			if (fProvideMembers && element instanceof ISourceReference
					&& element instanceof IParent) {
				if (fProvideWorkingCopy && element instanceof ICompilationUnit) {
					element = JavaModelUtil
							.toWorkingCopy((ICompilationUnit) element);
				}
				return ((IParent) element).getChildren();
			}
		} catch (JavaModelException e) {
			return NO_CHILDREN;
		}
		return NO_CHILDREN;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ITreeContentProvider
	 */
	public boolean hasChildren(Object element) {
		if (fProvideMembers) {
			// assume CUs and class files are never empty
			if (element instanceof ICompilationUnit) {
				// ||
				// element instanceof IClassFile) {
				return true;
			}
		} else {
			// don't allow to drill down into a compilation unit or class file
			if (element instanceof ICompilationUnit ||
			// element instanceof IClassFile ||
					element instanceof IFile)
				return false;
		}

		if (element instanceof IJavaProject) {
			IJavaProject jp = (IJavaProject) element;
			if (!jp.getProject().isOpen()) {
				return false;
			}
		}

		if (element instanceof IParent) {
			try {
				// when we have Java children return true, else we fetch all the
				// children
				if (((IParent) element).hasChildren())
					return true;
			} catch (JavaModelException e) {
				return true;
			}
		}
		Object[] children = getChildren(element);
		return (children != null) && children.length > 0;
	}

	/*
	 * (non-Javadoc) Method declared on ITreeContentProvider.
	 */
	public Object getParent(Object element) {
		if (!exists(element))
			return null;
		return internalGetParent(element);
	}

	private Object[] getPackageFragments(IPackageFragmentRoot root)
			throws JavaModelException {
		IJavaElement[] fragments = root.getChildren();
		// Object[] nonJavaResources= root.getNonJavaResources();
		// if (nonJavaResources == null)
		return fragments;
		// return concatenate(fragments, nonJavaResources);
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	// protected Object[] getPackageFragmentRoots(IJavaProject project) throws
	// JavaModelException {
	// if (!project.getProject().isOpen())
	// return NO_CHILDREN;
	//			
	// IPackageFragmentRoot[] roots= project.getPackageFragmentRoots();
	// List list= new ArrayList(roots.length);
	// // filter out package fragments that correspond to projects and
	// // replace them with the package fragments directly
	// for (int i= 0; i < roots.length; i++) {
	// IPackageFragmentRoot root= (IPackageFragmentRoot)roots[i];
	// if (isProjectPackageFragmentRoot(root)) {
	// Object[] children= root.getChildren();
	// for (int k= 0; k < children.length; k++)
	// list.add(children[k]);
	// }
	// else if (hasChildren(root)) {
	// list.add(root);
	// }
	// }
	// return concatenate(list.toArray(), project.getNonJavaResources());
	// }
	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected Object[] getJavaProjects(IJavaModel jm) throws JavaModelException {
		return jm.getJavaProjects();
	}

	// private Object[] getPackageContents(IPackageFragment fragment) throws
	// JavaModelException {
	// if (fragment.getKind() == IPackageFragmentRoot.K_SOURCE) {
	// return concatenate(fragment.getCompilationUnits(),
	// fragment.getNonJavaResources());
	// }
	// return concatenate(fragment.getClassFiles(),
	// fragment.getNonJavaResources());
	// }

	private Object[] getResources(IFolder folder) {
		try {
			// filter out folders that are package fragment roots
			Object[] members = folder.members();
			List nonJavaResources = new ArrayList();
			for (int i = 0; i < members.length; i++) {
				Object o = members[i];
				// A folder can also be a package fragement root in the
				// following case
				// Project
				// + src <- source folder
				// + excluded <- excluded from class path
				// + included <- a new source folder.
				// Included is a member of excluded, but since it is rendered as
				// a source
				// folder we have to exclude it as a normal child.
				if (o instanceof IFolder) {
					IJavaElement element = JavaCore.create((IFolder) o);
					if (element instanceof IPackageFragmentRoot
							&& element.exists()) {
						continue;
					}
				}
				nonJavaResources.add(o);
			}
			return nonJavaResources.toArray();
		} catch (CoreException e) {
			return NO_CHILDREN;
		}
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected boolean isClassPathChange(IJavaElementDelta delta) {

		// need to test the flags only for package fragment roots
		if (delta.getElement().getElementType() != IJavaElement.PACKAGE_FRAGMENT_ROOT)
			return false;

		int flags = delta.getFlags();
		return (delta.getKind() == IJavaElementDelta.CHANGED
				&& ((flags & IJavaElementDelta.F_ADDED_TO_CLASSPATH) != 0)
				|| ((flags & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) != 0) || ((flags & IJavaElementDelta.F_REORDER) != 0));
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected Object skipProjectPackageFragmentRoot(IPackageFragmentRoot root) {
		try {
			if (isProjectPackageFragmentRoot(root))
				return root.getParent();
			return root;
		} catch (JavaModelException e) {
			return root;
		}
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected boolean isPackageFragmentEmpty(IJavaElement element)
			throws JavaModelException {
		if (element instanceof IPackageFragment) {
			IPackageFragment fragment = (IPackageFragment) element;
			if (!(fragment.hasChildren()))
				// ||
				// fragment.getNonJavaResources().length > 0) &&
				// fragment.hasSubpackages())
				return true;
		}
		return false;
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected boolean isProjectPackageFragmentRoot(IPackageFragmentRoot root)
			throws JavaModelException {
		IResource resource = root.getResource();
		return (resource instanceof IProject);
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected boolean exists(Object element) {
		if (element == null) {
			return false;
		}
		if (element instanceof IResource) {
			return ((IResource) element).exists();
		}
		if (element instanceof IJavaElement) {
			return ((IJavaElement) element).exists();
		}
		return true;
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected Object internalGetParent(Object element) {
		if (element instanceof IJavaProject) {
			return ((IJavaProject) element).getJavaModel();
		}
		// try to map resources to the containing package fragment
		if (element instanceof IResource) {
			IResource parent = ((IResource) element).getParent();
			IJavaElement jParent = JavaCore.create(parent);
			// http://bugs.eclipse.org/bugs/show_bug.cgi?id=31374
			if (jParent != null && jParent.exists())
				return jParent;
			return parent;
		}

		// for package fragments that are contained in a project package
		// fragment
		// we have to skip the package fragment root as the parent.
		if (element instanceof IPackageFragment) {
			IPackageFragmentRoot parent = (IPackageFragmentRoot) ((IPackageFragment) element)
					.getParent();
			return skipProjectPackageFragmentRoot(parent);
		}
		if (element instanceof IJavaElement) {
			IJavaElement candidate = ((IJavaElement) element).getParent();
			// If the parent is a CU we might have shown working copy elements
			// below CU level. If so
			// return the original element instead of the working copy.
			if (candidate != null
					&& candidate.getElementType() == IJavaElement.COMPILATION_UNIT) {
				candidate = JavaModelUtil
						.toOriginal((ICompilationUnit) candidate);
			}
			return candidate;
		}
		return null;
	}

	/**
	 * Note: This method is for internal use only. Clients should not call this
	 * method.
	 */
	protected static Object[] concatenate(Object[] a1, Object[] a2) {
		int a1Len = a1.length;
		int a2Len = a2.length;
		Object[] res = new Object[a1Len + a2Len];
		System.arraycopy(a1, 0, res, 0, a1Len);
		System.arraycopy(a2, 0, res, a1Len, a2Len);
		return res;
	}

}
