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

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

import org.eclipse.core.resources.*;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.*;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.ITypeNameRequestor;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.util.Util;

/**
 * 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 implements SuffixConstants {
	/**
	 * 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[] packageFragmentRoots;

	/**
	 * 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 packageFragments;

	/**
	 * 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.
	 */
	protected HashMap unitsToLookInside;

	public NameLookup(IPackageFragmentRoot[] packageFragmentRoots, HashMap packageFragments, ICompilationUnit[] workingCopies) {
		this.packageFragmentRoots = packageFragmentRoots;
		this.packageFragments = packageFragments;
		if (workingCopies != null) {
			this.unitsToLookInside = new HashMap();
			for (int i = 0, length = workingCopies.length; i < length; i++) {
				ICompilationUnit unitToLookInside = workingCopies[i];
				ICompilationUnit original = unitToLookInside.getPrimary();
				this.unitsToLookInside.put(original, unitToLookInside);
			}
		}
	}

	/**
	 * 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 || acceptFlags == (ACCEPT_CLASSES | ACCEPT_INTERFACES))
			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.
		}
	}

	/**
	 * 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= this.packageFragmentRoots.length;
		for (int i= 0; i < count; i++) {
			if (requestor.isCanceled())
				return;
			IPackageFragmentRoot root= this.packageFragmentRoots[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 += SUFFIX_STRING_java;
		IPackageFragment[] frags= (IPackageFragment[]) this.packageFragments.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 = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
		if (possibleFragment == null) {
			//external jar
			for (int i = 0; i < this.packageFragmentRoots.length; i++) {
				IPackageFragmentRoot root = this.packageFragmentRoots[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;
			}
			switch (fromFactory.getElementType()) {
				case IJavaElement.PACKAGE_FRAGMENT:
					return (IPackageFragment) fromFactory;
				case IJavaElement.JAVA_PROJECT:
					// 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[]) this.packageFragments.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;
				case IJavaElement.PACKAGE_FRAGMENT_ROOT:
					return ((IPackageFragmentRoot)fromFactory).getPackageFragment(IPackageFragment.DEFAULT_PACKAGE_NAME);
			}
		}
		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= this.packageFragmentRoots.length;
		if (partialMatch) {
			name= name.toLowerCase();
			for (int i= 0; i < count; i++) {
				IPackageFragmentRoot root= this.packageFragmentRoots[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[]) this.packageFragments.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.length() == 0) {
			packageName= IPackageFragment.DEFAULT_PACKAGE_NAME;
		} else if (typeName.length() > 0 && Character.isLowerCase(typeName.charAt(0))) {
			// see if this is a known package and not a type
			if (findPackageFragments(packageName + "." + typeName, false) != null) return null; //$NON-NLS-1$
		}
		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 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).
		SingleTypeRequestor typeRequestor = new SingleTypeRequestor();
		seekTypes(name, pkg, partialMatch, acceptFlags, typeRequestor);
		IType type = typeRequestor.getType();
//		if (type == null)
//			type = findSecondaryType(name, pkg, partialMatch, acceptFlags);
		return type;
	}

	// TODO (kent) enable once index support is in
	IType findSecondaryType(String typeName, IPackageFragment pkg, boolean partialMatch, final int acceptFlags) {
		try {
			final ArrayList paths = new ArrayList();
			ITypeNameRequestor nameRequestor = new ITypeNameRequestor() {
				public void acceptClass(char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path) {
					if ((acceptFlags & ACCEPT_CLASSES) != 0)
						if (enclosingTypeNames == null || enclosingTypeNames.length == 0) // accept only top level types
							paths.add(path);
				}
				public void acceptInterface(char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path) {
					if ((acceptFlags & ACCEPT_INTERFACES) != 0)
						if (enclosingTypeNames == null || enclosingTypeNames.length == 0) // accept only top level types
							paths.add(path);
				}
			};

			int matchMode = partialMatch ? SearchPattern.R_PREFIX_MATCH : SearchPattern.R_EXACT_MATCH;
			int matchRule = !partialMatch ? matchMode | SearchPattern.R_CASE_SENSITIVE : matchMode;
			new SearchEngine().searchAllTypeNames(
				pkg.getElementName().toCharArray(),
				typeName.toCharArray(),
				matchRule,
				IJavaSearchConstants.TYPE,
				SearchEngine.createJavaSearchScope(new IJavaElement[] {pkg}, false),
				nameRequestor,
				IJavaSearchConstants.CANCEL_IF_NOT_READY_TO_SEARCH,
				null);

			if (!paths.isEmpty()) {
				IWorkspace workspace = ResourcesPlugin.getWorkspace();
				for (int i = 0, l = paths.size(); i < l; i++) {
					String pathname = (String) paths.get(i);
					if (org.eclipse.jdt.internal.compiler.util.Util.isJavaFileName(pathname)) {
						IFile file = workspace.getRoot().getFile(new Path(pathname));
						ICompilationUnit unit = JavaCore.createCompilationUnitFrom(file);
						return unit.getType(typeName);
					}
				}
			}
		} catch (JavaModelException e) {
			// ignore
		} catch (OperationCanceledException ignore) {
			// ignore
		}
		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= this.packageFragmentRoots.length;
		String matchName= partialMatch ? name.toLowerCase() : name;
		for (int i= 0; i < count; i++) {
			if (requestor.isCanceled())
				return;
			IPackageFragmentRoot root= this.packageFragmentRoots[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
		}

		// replace with working copies to look inside
		int length= compilationUnits.length;
		boolean[] isWorkingCopy = new boolean[length];
		int workingCopiesSize;
		if (this.unitsToLookInside != null && (workingCopiesSize = this.unitsToLookInside.size()) > 0) {
			Map temp = new HashMap(workingCopiesSize);
			temp.putAll(this.unitsToLookInside);
			for (int i = 0; i < length; i++) {
				ICompilationUnit unit = compilationUnits[i];
				ICompilationUnit workingCopy = (ICompilationUnit)temp.remove(unit);
				if (workingCopy != null) {
					compilationUnits[i] = workingCopy;
					isWorkingCopy[i] = true;
				}
			}
			// add remaining working copies that belong to this package
			int index = 0;
			Collection values = temp.values();
			Iterator iterator = values.iterator();
			while (iterator.hasNext()) {
				ICompilationUnit workingCopy = (ICompilationUnit)iterator.next();
				if (pkg.equals(workingCopy.getParent())) {
					if (index == 0) {
						int valuesLength = values.size();
						index = length;
						length += valuesLength;
						System.arraycopy(compilationUnits, 0, compilationUnits = new ICompilationUnit[length], 0, index);
						System.arraycopy(isWorkingCopy, 0, isWorkingCopy = new boolean[length], 0, index);
					}
					isWorkingCopy[index] = true; 
					compilationUnits[index++] = workingCopy;
				}
			}
			if (index > 0 && index < length) {
				System.arraycopy(compilationUnits, 0, compilationUnits = new ICompilationUnit[index], 0, index);
				System.arraycopy(isWorkingCopy, 0, isWorkingCopy = new boolean[index], 0, index);
				length = index;
			}
		}
			
		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 + SUFFIX_STRING_java;
		String potentialUnitName = null;
		if (potentialMemberType) {
			potentialUnitName = partialMatch ? potentialMatchName.toLowerCase() : potentialMatchName + SUFFIX_STRING_java;
		}

		for (int i= 0; i < length; i++) {
			if (requestor.isCanceled())
				return;
			ICompilationUnit compilationUnit= compilationUnits[i];
			
			if ((isWorkingCopy[i] && !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);
					}
				}
			}

		}
	}

	/**
	 * 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);
				}
		}
	}
}
