/******************************************************************************* * 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 NameLookup 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. * *

* Name lookup only returns a handle when the named element actually exists in * the model; otherwise null is returned. * *

* There are two logical sets of methods within this interface. Methods which * start with find* 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 * seek*. These methods do comprehensive searches of the * IJavaProject returning hits in real time through an * IJavaElementRequestor. * */ 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 IPackageFragmentRoot'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 IWorkspace 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: *

* 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 NameLookup based on the info of the given * IJavaProject. * * @throws JavaModelException * if the IJavaProject 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. * *

* The partialMatch 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 ICompilationUnit which defines the type named * qualifiedTypeName, or null if none exists. * The domain of the search is bounded by the classpath of the * IJavaProject this NameLookup was obtained * from. *

* 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 null if none exist. The domain of the search is * bounded by the classpath of the IJavaProject this * NameLookup 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 null if none exist. * * The name can be: - empty: "" - qualified: "pack.pack1.pack2" * * @param partialMatch * partial name matches qualify when true, only * exact name matches qualify when false */ 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 null if none exist. Specifying a * null 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 true, only * exact name matches qualify when false * @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 null * 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 true, only * exact name matches qualify when false * @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 * searchName, otherwise false. * *

* The partialMatch 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 * IJavaProject this NameLookup was obtained * from. * * @param partialMatch * partial name matches qualify when true; only * exact name matches qualify when false */ 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 null, 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 true; only * exact name matches qualify when false * @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. null 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 true, only * exact name matches qualify when false */ 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); } } } }