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