/*******************************************************************************
 * 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.internal.core;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import net.sourceforge.phpdt.core.IClasspathEntry;
import net.sourceforge.phpdt.core.ICompilationUnit;
import net.sourceforge.phpdt.core.IJavaElement;
import net.sourceforge.phpdt.core.IJavaProject;
import net.sourceforge.phpdt.core.IPackageFragment;
import net.sourceforge.phpdt.core.IPackageFragmentRoot;
import net.sourceforge.phpdt.core.IType;
import net.sourceforge.phpdt.core.IWorkingCopy;
import net.sourceforge.phpdt.core.JavaCore;
import net.sourceforge.phpdt.core.JavaModelException;
import net.sourceforge.phpdt.internal.core.util.PerThreadObject;
import net.sourceforge.phpdt.internal.core.util.Util;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;

/**
 * A <code>NameLookup</code> provides name resolution within a Java project.
 * The name lookup facility uses the project's classpath to prioritize the order
 * in which package fragments are searched when resolving a name.
 * 
 * <p>
 * Name lookup only returns a handle when the named element actually exists in
 * the model; otherwise <code>null</code> is returned.
 * 
 * <p>
 * There are two logical sets of methods within this interface. Methods which
 * start with <code>find*</code> are intended to be convenience methods for
 * quickly finding an element within another element; for instance, for finding
 * a class within a package. The other set of methods all begin with
 * <code>seek*</code>. These methods do comprehensive searches of the
 * <code>IJavaProject</code> returning hits in real time through an
 * <code>IJavaElementRequestor</code>.
 * 
 */
public class NameLookup {
	/**
	 * Accept flag for specifying classes.
	 */
	public static final int ACCEPT_CLASSES = 0x00000002;

	/**
	 * Accept flag for specifying interfaces.
	 */
	public static final int ACCEPT_INTERFACES = 0x00000004;

	/**
	 * The <code>IPackageFragmentRoot</code>'s associated with the classpath
	 * of this NameLookup facility's project.
	 */
	protected IPackageFragmentRoot[] fPackageFragmentRoots = null;

	/**
	 * Table that maps package names to lists of package fragments for all
	 * package fragments in the package fragment roots known by this name lookup
	 * facility. To allow > 1 package fragment with the same name, values are
	 * arrays of package fragments ordered as they appear on the classpath.
	 */
	protected Map fPackageFragments;

	/**
	 * The <code>IWorkspace</code> that this NameLookup is configure within.
	 */
	protected IWorkspace workspace;

	/**
	 * A map from compilation unit handles to units to look inside (compilation
	 * units or working copies). Allows working copies to take precedence over
	 * compilation units. The cache is a 2-level cache, first keyed by thread.
	 */
	protected PerThreadObject unitsToLookInside = new PerThreadObject();

	public NameLookup(IJavaProject project) throws JavaModelException {
		configureFromProject(project);
	}

	/**
	 * Returns true if:
	 * <ul>
	 * <li>the given type is an existing class and the flag's
	 * <code>ACCEPT_CLASSES</code> bit is on
	 * <li>the given type is an existing interface and the
	 * <code>ACCEPT_INTERFACES</code> bit is on
	 * <li>neither the <code>ACCEPT_CLASSES</code> or
	 * <code>ACCEPT_INTERFACES</code> bit is on
	 * </ul>
	 * Otherwise, false is returned.
	 */
	protected boolean acceptType(IType type, int acceptFlags) {
		if (acceptFlags == 0)
			return true; // no flags, always accepted
		try {
			if (type.isClass()) {
				return (acceptFlags & ACCEPT_CLASSES) != 0;
			} else {
				return (acceptFlags & ACCEPT_INTERFACES) != 0;
			}
		} catch (JavaModelException npe) {
			return false; // the class is not present, do not accept.
		}
	}

	/**
	 * Configures this <code>NameLookup</code> based on the info of the given
	 * <code>IJavaProject</code>.
	 * 
	 * @throws JavaModelException
	 *             if the <code>IJavaProject</code> has no classpath.
	 */
	private void configureFromProject(IJavaProject project)
			throws JavaModelException {
		workspace = ResourcesPlugin.getWorkspace();
		fPackageFragmentRoots = ((JavaProject) project)
				.getAllPackageFragmentRoots();
		fPackageFragments = new HashMap();
		IPackageFragment[] frags = this.getPackageFragmentsInRoots(
				fPackageFragmentRoots, project);
		for (int i = 0; i < frags.length; i++) {
			IPackageFragment fragment = frags[i];
			IPackageFragment[] entry = (IPackageFragment[]) fPackageFragments
					.get(fragment.getElementName());
			if (entry == null) {
				entry = new IPackageFragment[1];
				entry[0] = fragment;
				fPackageFragments.put(fragment.getElementName(), entry);
			} else {
				IPackageFragment[] copy = new IPackageFragment[entry.length + 1];
				System.arraycopy(entry, 0, copy, 0, entry.length);
				copy[entry.length] = fragment;
				fPackageFragments.put(fragment.getElementName(), copy);
			}
		}
	}

	/**
	 * Finds every type in the project whose simple name matches the prefix,
	 * informing the requestor of each hit. The requestor is polled for
	 * cancellation at regular intervals.
	 * 
	 * <p>
	 * The <code>partialMatch</code> argument indicates partial matches should
	 * be considered.
	 */
	private void findAllTypes(String prefix, boolean partialMatch,
			int acceptFlags, IJavaElementRequestor requestor) {
		int count = fPackageFragmentRoots.length;
		for (int i = 0; i < count; i++) {
			if (requestor.isCanceled())
				return;
			IPackageFragmentRoot root = fPackageFragmentRoots[i];
			IJavaElement[] packages = null;
			try {
				packages = root.getChildren();
			} catch (JavaModelException npe) {
				continue; // the root is not present, continue;
			}
			if (packages != null) {
				for (int j = 0, packageCount = packages.length; j < packageCount; j++) {
					if (requestor.isCanceled())
						return;
					seekTypes(prefix, (IPackageFragment) packages[j],
							partialMatch, acceptFlags, requestor);
				}
			}
		}
	}

	/**
	 * Returns the <code>ICompilationUnit</code> which defines the type named
	 * <code>qualifiedTypeName</code>, or <code>null</code> if none exists.
	 * The domain of the search is bounded by the classpath of the
	 * <code>IJavaProject</code> this <code>NameLookup</code> was obtained
	 * from.
	 * <p>
	 * The name must be fully qualified (eg "java.lang.Object",
	 * "java.util.Hashtable$Entry")
	 */
	public ICompilationUnit findCompilationUnit(String qualifiedTypeName) {
		String pkgName = IPackageFragment.DEFAULT_PACKAGE_NAME;
		String cuName = qualifiedTypeName;

		int index = qualifiedTypeName.lastIndexOf('.');
		if (index != -1) {
			pkgName = qualifiedTypeName.substring(0, index);
			cuName = qualifiedTypeName.substring(index + 1);
		}
		index = cuName.indexOf('$');
		if (index != -1) {
			cuName = cuName.substring(0, index);
		}
		cuName += ".java"; //$NON-NLS-1$
		IPackageFragment[] frags = (IPackageFragment[]) fPackageFragments
				.get(pkgName);
		if (frags != null) {
			for (int i = 0; i < frags.length; i++) {
				IPackageFragment frag = frags[i];
				// if (!(frag instanceof JarPackageFragment)) {
				// ICompilationUnit cu= frag.getCompilationUnit(cuName);
				// if (cu != null && cu.exists()) {
				// return cu;
				// }
				// }
			}
		}
		return null;
	}

	/**
	 * Returns the package fragment whose path matches the given (absolute)
	 * path, or <code>null</code> if none exist. The domain of the search is
	 * bounded by the classpath of the <code>IJavaProject</code> this
	 * <code>NameLookup</code> was obtained from. The path can be: - internal
	 * to the workbench: "/Project/src" - external to the workbench:
	 * "c:/jdk/classes.zip/java/lang"
	 */
	public IPackageFragment findPackageFragment(IPath path) {
		if (!path.isAbsolute()) {
			throw new IllegalArgumentException(Util.bind("path.mustBeAbsolute")); //$NON-NLS-1$
		}
		/*
		 * this code should rather use the package fragment map to find the
		 * candidate package, then check if the respective enclosing root maps
		 * to the one on this given IPath.
		 */
		IResource possibleFragment = workspace.getRoot().findMember(path);
		if (possibleFragment == null) {
			// external jar
			for (int i = 0; i < fPackageFragmentRoots.length; i++) {
				IPackageFragmentRoot root = fPackageFragmentRoots[i];
				if (!root.isExternal()) {
					continue;
				}
				IPath rootPath = root.getPath();
				int matchingCount = rootPath.matchingFirstSegments(path);
				if (matchingCount != 0) {
					String name = path.toOSString();
					// + 1 is for the File.separatorChar
					name = name.substring(rootPath.toOSString().length() + 1,
							name.length());
					name = name.replace(File.separatorChar, '.');
					IJavaElement[] list = null;
					try {
						list = root.getChildren();
					} catch (JavaModelException npe) {
						continue; // the package fragment root is not present;
					}
					int elementCount = list.length;
					for (int j = 0; j < elementCount; j++) {
						IPackageFragment packageFragment = (IPackageFragment) list[j];
						if (nameMatches(name, packageFragment, false)) {
							return packageFragment;
						}
					}
				}
			}
		} else {
			IJavaElement fromFactory = JavaCore.create(possibleFragment);
			if (fromFactory == null) {
				return null;
			}
			if (fromFactory instanceof IPackageFragment) {
				return (IPackageFragment) fromFactory;
			} else if (fromFactory instanceof IJavaProject) {
				// default package in a default root
				JavaProject project = (JavaProject) fromFactory;
				try {
					IClasspathEntry entry = project.getClasspathEntryFor(path);
					if (entry != null) {
						IPackageFragmentRoot root = project
								.getPackageFragmentRoot(project.getResource());
						IPackageFragment[] pkgs = (IPackageFragment[]) fPackageFragments
								.get(IPackageFragment.DEFAULT_PACKAGE_NAME);
						if (pkgs == null) {
							return null;
						}
						for (int i = 0; i < pkgs.length; i++) {
							if (pkgs[i].getParent().equals(root)) {
								return pkgs[i];
							}
						}
					}
				} catch (JavaModelException e) {
					return null;
				}
			}
		}
		return null;
	}

	/**
	 * Returns the package fragments whose name matches the given (qualified)
	 * name, or <code>null</code> if none exist.
	 * 
	 * The name can be: - empty: "" - qualified: "pack.pack1.pack2"
	 * 
	 * @param partialMatch
	 *            partial name matches qualify when <code>true</code>, only
	 *            exact name matches qualify when <code>false</code>
	 */
	public IPackageFragment[] findPackageFragments(String name,
			boolean partialMatch) {
		int count = fPackageFragmentRoots.length;
		if (partialMatch) {
			name = name.toLowerCase();
			for (int i = 0; i < count; i++) {
				IPackageFragmentRoot root = fPackageFragmentRoots[i];
				IJavaElement[] list = null;
				try {
					list = root.getChildren();
				} catch (JavaModelException npe) {
					continue; // the package fragment root is not present;
				}
				int elementCount = list.length;
				IPackageFragment[] result = new IPackageFragment[elementCount];
				int resultLength = 0;
				for (int j = 0; j < elementCount; j++) {
					IPackageFragment packageFragment = (IPackageFragment) list[j];
					if (nameMatches(name, packageFragment, true)) {
						result[resultLength++] = packageFragment;
					}
				}
				if (resultLength > 0) {
					System.arraycopy(result, 0,
							result = new IPackageFragment[resultLength], 0,
							resultLength);
					return result;
				} else {
					return null;
				}
			}
		} else {
			IPackageFragment[] fragments = (IPackageFragment[]) fPackageFragments
					.get(name);
			if (fragments != null) {
				IPackageFragment[] result = new IPackageFragment[fragments.length];
				int resultLength = 0;
				for (int i = 0; i < fragments.length; i++) {
					IPackageFragment packageFragment = fragments[i];
					result[resultLength++] = packageFragment;
				}
				if (resultLength > 0) {
					System.arraycopy(result, 0,
							result = new IPackageFragment[resultLength], 0,
							resultLength);
					return result;
				} else {
					return null;
				}
			}
		}
		return null;
	}

	/**
	 * 
	 */
	public IType findType(String typeName, String packageName,
			boolean partialMatch, int acceptFlags) {
		if (packageName == null) {
			packageName = IPackageFragment.DEFAULT_PACKAGE_NAME;
		}
		JavaElementRequestor elementRequestor = new JavaElementRequestor();
		seekPackageFragments(packageName, false, elementRequestor);
		IPackageFragment[] packages = elementRequestor.getPackageFragments();

		for (int i = 0, length = packages.length; i < length; i++) {
			IType type = findType(typeName, packages[i], partialMatch,
					acceptFlags);
			if (type != null)
				return type;
		}
		return null;
	}

	/**
	 * Returns all the package fragments found in the specified package fragment
	 * roots. Make sure the returned fragments have the given project as great
	 * parent. This ensures the name lookup will not refer to another project
	 * (through jar package fragment roots)
	 */
	private IPackageFragment[] getPackageFragmentsInRoots(
			IPackageFragmentRoot[] roots, IJavaProject project) {

		// The following code assumes that all the roots have the given project
		// as their parent
		ArrayList frags = new ArrayList();
		for (int i = 0; i < roots.length; i++) {
			IPackageFragmentRoot root = roots[i];
			try {
				IJavaElement[] children = root.getChildren();

				/*
				 * 2 jar package fragment roots can be equals but not belonging
				 * to the same project. As a result, they share the same element
				 * info. So this jar package fragment root could get the
				 * children of another jar package fragment root. The following
				 * code ensures that the children of this jar package fragment
				 * root have the given project as a great parent.
				 */
				int length = children.length;
				if (length == 0)
					continue;
				if (children[0].getParent().getParent().equals(project)) {
					// the children have the right parent, simply add them to
					// the list
					for (int j = 0; j < length; j++) {
						frags.add(children[j]);
					}
				} else {
					// create a new handle with the root as the parent
					for (int j = 0; j < length; j++) {
						frags.add(root.getPackageFragment(children[j]
								.getElementName()));
					}
				}
			} catch (JavaModelException e) {
				// do nothing
			}
		}
		IPackageFragment[] fragments = new IPackageFragment[frags.size()];
		frags.toArray(fragments);
		return fragments;
	}

	/**
	 * Returns the first type in the given package whose name matches the given
	 * (unqualified) name, or <code>null</code> if none exist. Specifying a
	 * <code>null</code> package will result in no matches. The domain of the
	 * search is bounded by the Java project from which this name lookup was
	 * obtained.
	 * 
	 * @param name
	 *            the name of the type to find
	 * @param pkg
	 *            the package to search
	 * @param partialMatch
	 *            partial name matches qualify when <code>true</code>, only
	 *            exact name matches qualify when <code>false</code>
	 * @param acceptFlags
	 *            a bit mask describing if classes, interfaces or both classes
	 *            and interfaces are desired results. If no flags are specified,
	 *            all types are returned.
	 * 
	 * @see #ACCEPT_CLASSES
	 * @see #ACCEPT_INTERFACES
	 */
	public IType findType(String name, IPackageFragment pkg,
			boolean partialMatch, int acceptFlags) {
		if (pkg == null) {
			return null;
		}
		// Return first found (ignore duplicates).
		// synchronized(JavaModelManager.getJavaModelManager()){
		// SingleTypeRequestor typeRequestor = new SingleTypeRequestor();
		// seekTypes(name, pkg, partialMatch, acceptFlags, typeRequestor);
		// IType type= typeRequestor.getType();
		// return type;
		// }
		return null;
	}

	/**
	 * Returns the type specified by the qualified name, or <code>null</code>
	 * if none exist. The domain of the search is bounded by the Java project
	 * from which this name lookup was obtained.
	 * 
	 * @param name
	 *            the name of the type to find
	 * @param partialMatch
	 *            partial name matches qualify when <code>true</code>, only
	 *            exact name matches qualify when <code>false</code>
	 * @param acceptFlags
	 *            a bit mask describing if classes, interfaces or both classes
	 *            and interfaces are desired results. If no flags are specified,
	 *            all types are returned.
	 * 
	 * @see #ACCEPT_CLASSES
	 * @see #ACCEPT_INTERFACES
	 */
	public IType findType(String name, boolean partialMatch, int acceptFlags) {
		int index = name.lastIndexOf('.');
		String className = null, packageName = null;
		if (index == -1) {
			packageName = IPackageFragment.DEFAULT_PACKAGE_NAME;
			className = name;
		} else {
			packageName = name.substring(0, index);
			className = name.substring(index + 1);
		}
		return findType(className, packageName, partialMatch, acceptFlags);
	}

	/**
	 * Returns true if the given element's name matches the specified
	 * <code>searchName</code>, otherwise false.
	 * 
	 * <p>
	 * The <code>partialMatch</code> argument indicates partial matches should
	 * be considered. NOTE: in partialMatch mode, the case will be ignored, and
	 * the searchName must already have been lowercased.
	 */
	protected boolean nameMatches(String searchName, IJavaElement element,
			boolean partialMatch) {
		if (partialMatch) {
			// partial matches are used in completion mode, thus case
			// insensitive mode
			return element.getElementName().toLowerCase()
					.startsWith(searchName);
		} else {
			return element.getElementName().equals(searchName);
		}
	}

	/**
	 * Notifies the given requestor of all package fragments with the given
	 * name. Checks the requestor at regular intervals to see if the requestor
	 * has canceled. The domain of the search is bounded by the
	 * <code>IJavaProject</code> this <code>NameLookup</code> was obtained
	 * from.
	 * 
	 * @param partialMatch
	 *            partial name matches qualify when <code>true</code>; only
	 *            exact name matches qualify when <code>false</code>
	 */
	public void seekPackageFragments(String name, boolean partialMatch,
			IJavaElementRequestor requestor) {
		int count = fPackageFragmentRoots.length;
		String matchName = partialMatch ? name.toLowerCase() : name;
		for (int i = 0; i < count; i++) {
			if (requestor.isCanceled())
				return;
			IPackageFragmentRoot root = fPackageFragmentRoots[i];
			IJavaElement[] list = null;
			try {
				list = root.getChildren();
			} catch (JavaModelException npe) {
				continue; // this root package fragment is not present
			}
			int elementCount = list.length;
			for (int j = 0; j < elementCount; j++) {
				if (requestor.isCanceled())
					return;
				IPackageFragment packageFragment = (IPackageFragment) list[j];
				if (nameMatches(matchName, packageFragment, partialMatch))
					requestor.acceptPackageFragment(packageFragment);
			}
		}
	}

	/**
	 * Notifies the given requestor of all types (classes and interfaces) in the
	 * given package fragment with the given (unqualified) name. Checks the
	 * requestor at regular intervals to see if the requestor has canceled. If
	 * the given package fragment is <code>null</code>, all types in the
	 * project whose simple name matches the given name are found.
	 * 
	 * @param name
	 *            The name to search
	 * @param pkg
	 *            The corresponding package fragment
	 * @param partialMatch
	 *            partial name matches qualify when <code>true</code>; only
	 *            exact name matches qualify when <code>false</code>
	 * @param acceptFlags
	 *            a bit mask describing if classes, interfaces or both classes
	 *            and interfaces are desired results. If no flags are specified,
	 *            all types are returned.
	 * @param requestor
	 *            The requestor that collects the result
	 * 
	 * @see #ACCEPT_CLASSES
	 * @see #ACCEPT_INTERFACES
	 */
	public void seekTypes(String name, IPackageFragment pkg,
			boolean partialMatch, int acceptFlags,
			IJavaElementRequestor requestor) {

		String matchName = partialMatch ? name.toLowerCase() : name;
		if (matchName.indexOf('.') >= 0) { // looks for member type A.B
			matchName = matchName.replace('.', '$');
		}
		if (pkg == null) {
			findAllTypes(matchName, partialMatch, acceptFlags, requestor);
			return;
		}
		IPackageFragmentRoot root = (IPackageFragmentRoot) pkg.getParent();
		try {
			int packageFlavor = root.getKind();
			switch (packageFlavor) {
			// case IPackageFragmentRoot.K_BINARY :
			// seekTypesInBinaryPackage(matchName, pkg, partialMatch,
			// acceptFlags, requestor);
			// break;
			case IPackageFragmentRoot.K_SOURCE:
				seekTypesInSourcePackage(matchName, pkg, partialMatch,
						acceptFlags, requestor);
				break;
			default:
				return;
			}
		} catch (JavaModelException e) {
			return;
		}
	}

	/**
	 * Performs type search in a binary package.
	 */
	// protected void seekTypesInBinaryPackage(String name, IPackageFragment
	// pkg, boolean partialMatch, int acceptFlags, IJavaElementRequestor
	// requestor) {
	// IClassFile[] classFiles= null;
	// try {
	// classFiles= pkg.getClassFiles();
	// } catch (JavaModelException npe) {
	// return; // the package is not present
	// }
	// int length= classFiles.length;
	//
	// String unqualifiedName= name;
	// int index= name.lastIndexOf('$');
	// if (index != -1) {
	// //the type name of the inner type
	// unqualifiedName= name.substring(index + 1, name.length());
	// // unqualifiedName is empty if the name ends with a '$' sign.
	// // See http://dev.eclipse.org/bugs/show_bug.cgi?id=14642
	// if ((unqualifiedName.length() > 0 &&
	// Character.isDigit(unqualifiedName.charAt(0))) || unqualifiedName.length()
	// == 0){
	// unqualifiedName = name;
	// }
	// }
	// String matchName= partialMatch ? name.toLowerCase() : name;
	// for (int i= 0; i < length; i++) {
	// if (requestor.isCanceled())
	// return;
	// IClassFile classFile= classFiles[i];
	// String elementName = classFile.getElementName();
	// if (partialMatch) elementName = elementName.toLowerCase();
	//
	// /**
	// * Must use startWith because matchName will never have the
	// * extension ".class" and the elementName always will.
	// */
	// if (elementName.startsWith(matchName)) {
	// IType type= null;
	// try {
	// type= classFile.getType();
	// } catch (JavaModelException npe) {
	// continue; // the classFile is not present
	// }
	// if (!partialMatch || (type.getElementName().length() > 0 &&
	// !Character.isDigit(type.getElementName().charAt(0)))) { //not an
	// anonymous type
	// if (nameMatches(unqualifiedName, type, partialMatch) && acceptType(type,
	// acceptFlags))
	// requestor.acceptType(type);
	// }
	// }
	// }
	// }
	/**
	 * Performs type search in a source package.
	 */
	protected void seekTypesInSourcePackage(String name, IPackageFragment pkg,
			boolean partialMatch, int acceptFlags,
			IJavaElementRequestor requestor) {
		ICompilationUnit[] compilationUnits = null;
		try {
			compilationUnits = pkg.getCompilationUnits();
		} catch (JavaModelException npe) {
			return; // the package is not present
		}
		int length = compilationUnits.length;
		String matchName = name;
		int index = name.indexOf('$');
		boolean potentialMemberType = false;
		String potentialMatchName = null;
		if (index != -1) {
			// the compilation unit name of the inner type
			potentialMatchName = name.substring(0, index);
			potentialMemberType = true;
		}

		/**
		 * In the following, matchName will never have the extension ".java" and
		 * the compilationUnits always will. So add it if we're looking for an
		 * exact match.
		 */
		String unitName = partialMatch ? matchName.toLowerCase() : matchName
				+ ".java"; //$NON-NLS-1$
		String potentialUnitName = null;
		if (potentialMemberType) {
			potentialUnitName = partialMatch ? potentialMatchName.toLowerCase()
					: potentialMatchName + ".java"; //$NON-NLS-1$
		}

		for (int i = 0; i < length; i++) {
			if (requestor.isCanceled())
				return;
			ICompilationUnit compilationUnit = compilationUnits[i];

			// unit to look inside
			ICompilationUnit unitToLookInside = null;
			Map workingCopies = (Map) this.unitsToLookInside.getCurrent();
			if (workingCopies != null
					&& (unitToLookInside = (ICompilationUnit) workingCopies
							.get(compilationUnit)) != null) {
				compilationUnit = unitToLookInside;
			}
			if ((unitToLookInside != null && !potentialMemberType)
					|| nameMatches(unitName, compilationUnit, partialMatch)) {
				IType[] types = null;
				try {
					types = compilationUnit.getTypes();
				} catch (JavaModelException npe) {
					continue; // the compilation unit is not present
				}
				int typeLength = types.length;
				for (int j = 0; j < typeLength; j++) {
					if (requestor.isCanceled())
						return;
					IType type = types[j];
					if (nameMatches(matchName, type, partialMatch)) {
						if (acceptType(type, acceptFlags))
							requestor.acceptType(type);
					}
				}
			} else if (potentialMemberType
					&& nameMatches(potentialUnitName, compilationUnit,
							partialMatch)) {
				IType[] types = null;
				try {
					types = compilationUnit.getTypes();
				} catch (JavaModelException npe) {
					continue; // the compilation unit is not present
				}
				int typeLength = types.length;
				for (int j = 0; j < typeLength; j++) {
					if (requestor.isCanceled())
						return;
					IType type = types[j];
					if (nameMatches(potentialMatchName, type, partialMatch)) {
						seekQualifiedMemberTypes(name.substring(index + 1, name
								.length()), type, partialMatch, requestor,
								acceptFlags);
					}
				}
			}

		}
	}

	/**
	 * Remembers a set of compilation units that will be looked inside when
	 * looking up a type. If they are working copies, they take precedence of
	 * their compilation units. <code>null</code> means that no special
	 * compilation units should be used.
	 */
	public void setUnitsToLookInside(IWorkingCopy[] unitsToLookInside) {

		if (unitsToLookInside == null) {
			this.unitsToLookInside.setCurrent(null);
		} else {
			HashMap workingCopies = new HashMap();
			this.unitsToLookInside.setCurrent(workingCopies);
			for (int i = 0, length = unitsToLookInside.length; i < length; i++) {
				IWorkingCopy unitToLookInside = unitsToLookInside[i];
				ICompilationUnit original = (ICompilationUnit) unitToLookInside
						.getOriginalElement();
				if (original != null) {
					workingCopies.put(original, unitToLookInside);
				} else {
					workingCopies.put(unitToLookInside, unitToLookInside);
				}
			}
		}
	}

	/**
	 * Notifies the given requestor of all types (classes and interfaces) in the
	 * given type with the given (possibly qualified) name. Checks the requestor
	 * at regular intervals to see if the requestor has canceled.
	 * 
	 * @param partialMatch
	 *            partial name matches qualify when <code>true</code>, only
	 *            exact name matches qualify when <code>false</code>
	 */
	protected void seekQualifiedMemberTypes(String qualifiedName, IType type,
			boolean partialMatch, IJavaElementRequestor requestor,
			int acceptFlags) {
		if (type == null)
			return;
		IType[] types = null;
		try {
			types = type.getTypes();
		} catch (JavaModelException npe) {
			return; // the enclosing type is not present
		}
		String matchName = qualifiedName;
		int index = qualifiedName.indexOf('$');
		boolean nested = false;
		if (index != -1) {
			matchName = qualifiedName.substring(0, index);
			nested = true;
		}
		int length = types.length;
		for (int i = 0; i < length; i++) {
			if (requestor.isCanceled())
				return;
			IType memberType = types[i];
			if (nameMatches(matchName, memberType, partialMatch))
				if (nested) {
					seekQualifiedMemberTypes(qualifiedName.substring(index + 1,
							qualifiedName.length()), memberType, partialMatch,
							requestor, acceptFlags);
				} else {
					if (acceptType(memberType, acceptFlags))
						requestor.acceptMemberType(memberType);
				}
		}
	}
}
