| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.core; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.dltk.compiler.CharOperation; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.IBuildpathEntry; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IProjectFragment; |
| import org.eclipse.dltk.core.IScriptFolder; |
| import org.eclipse.dltk.core.IScriptProject; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.IType; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.environment.EnvironmentPathUtils; |
| import org.eclipse.dltk.internal.compiler.env.AccessRestriction; |
| import org.eclipse.dltk.internal.compiler.env.AccessRuleSet; |
| import org.eclipse.dltk.internal.core.util.HashtableOfArrayToObject; |
| import org.eclipse.dltk.internal.core.util.Messages; |
| import org.eclipse.dltk.internal.core.util.Util; |
| |
| /** |
| * A <code>NameLookup</code> provides name resolution within a Script 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>IScriptProject</code> returning hits in real time through an |
| * <code>IModelElementRequestor</code>. |
| * |
| */ |
| public class NameLookup { |
| public static class Answer { |
| public IType type; |
| AccessRestriction restriction; |
| |
| Answer(IType type, AccessRestriction restriction) { |
| this.type = type; |
| this.restriction = restriction; |
| } |
| |
| public boolean ignoreIfBetter() { |
| return this.restriction != null |
| && this.restriction.ignoreIfBetter(); |
| } |
| |
| /* |
| * Returns whether this answer is better than the other awswer. |
| * (accessible is better than discouraged, which is better than |
| * non-accessible) |
| */ |
| public boolean isBetter(Answer otherAnswer) { |
| if (otherAnswer == null) |
| return true; |
| if (this.restriction == null) |
| return true; |
| return otherAnswer.restriction != null |
| && this.restriction.getProblemId() < otherAnswer.restriction |
| .getProblemId(); |
| } |
| } |
| |
| /* |
| * Accept flag for all kinds of types |
| */ |
| |
| public static boolean VERBOSE = DLTKCore.VERBOSE_SEARCH_NAMELOOKUP; |
| |
| private static final IType[] NO_TYPES = {}; |
| |
| public static final int ACCEPT_ALL = 0; |
| |
| /** |
| * The <code>IScriptFolderRoot</code>'s associated with the buildpath of |
| * this NameLookup facility's project. |
| */ |
| protected IProjectFragment[] projectFragments; |
| |
| /** |
| * Table that maps package names to lists of package fragment roots that |
| * contain such a package known by this name lookup facility. To allow > 1 |
| * package fragment with the same name, values are arrays of package |
| * fragment roots ordered as they appear on the buildpath. Note if the list |
| * is of size 1, then the IScriptFolderRoot object replaces the array. |
| */ |
| protected HashtableOfArrayToObject scriptFolders; |
| |
| /* |
| * A set of names (String[]) that are known to be package names. Value is |
| * not null for known package. |
| */ |
| protected HashtableOfArrayToObject isPackageCache; |
| |
| /** |
| * Reverse map from root path to corresponding resolved CP entry (so as to |
| * be able to figure inclusion/exclusion rules) |
| */ |
| protected Map rootToResolvedEntries; |
| |
| /** |
| * A map from package handles to a map from type name to an IType or an |
| * IType[]. Allows working copies to take precedence over compilation units. |
| */ |
| protected HashMap typesInWorkingCopies; |
| |
| public long timeSpentInSeekTypesInSourcePackage = 0; |
| public long timeSpentInSeekTypesInBinaryPackage = 0; |
| |
| public NameLookup(IProjectFragment[] ProjectFragments, |
| HashtableOfArrayToObject ScriptFolders, |
| HashtableOfArrayToObject isPackage, ISourceModule[] workingCopies, |
| Map rootToResolvedEntries) { |
| long start = -1; |
| if (VERBOSE) { |
| Util.verbose(" BUILDING NameLoopkup"); //$NON-NLS-1$ |
| Util.verbose(" -> pkg roots size: " + (ProjectFragments == null ? 0 : ProjectFragments.length)); //$NON-NLS-1$ |
| Util.verbose(" -> pkgs size: " + (ScriptFolders == null ? 0 : ScriptFolders.size())); //$NON-NLS-1$ |
| Util.verbose(" -> working copy size: " + (workingCopies == null ? 0 : workingCopies.length)); //$NON-NLS-1$ |
| start = System.currentTimeMillis(); |
| } |
| this.projectFragments = ProjectFragments; |
| if (workingCopies == null) { |
| this.scriptFolders = ScriptFolders; |
| this.isPackageCache = isPackage; |
| } else { |
| // clone tables as we're adding packages from working copies |
| try { |
| this.scriptFolders = (HashtableOfArrayToObject) ScriptFolders |
| .clone(); |
| this.isPackageCache = (HashtableOfArrayToObject) isPackage |
| .clone(); |
| } catch (CloneNotSupportedException e1) { |
| // ignore (implementation of HashtableOfArrayToObject supports |
| // cloning) |
| } |
| this.typesInWorkingCopies = new HashMap(); |
| for (int i = 0, length = workingCopies.length; i < length; i++) { |
| ISourceModule workingCopy = workingCopies[i]; |
| IScriptFolder pkg = (IScriptFolder) workingCopy.getParent(); |
| HashMap typeMap = (HashMap) this.typesInWorkingCopies.get(pkg); |
| if (typeMap == null) { |
| typeMap = new HashMap(); |
| this.typesInWorkingCopies.put(pkg, typeMap); |
| } |
| try { |
| IType[] types = workingCopy.getTypes(); |
| int typeLength = types.length; |
| if (typeLength == 0) { |
| String typeName = workingCopy.getElementName(); |
| typeMap.put(typeName, NO_TYPES); |
| } else { |
| for (int j = 0; j < typeLength; j++) { |
| IType type = types[j]; |
| String typeName = type.getElementName(); |
| Object existing = typeMap.get(typeName); |
| if (existing == null) { |
| typeMap.put(typeName, type); |
| } else if (existing instanceof IType) { |
| typeMap.put(typeName, new IType[] { |
| (IType) existing, type }); |
| } else { |
| IType[] existingTypes = (IType[]) existing; |
| int existingTypeLength = existingTypes.length; |
| System.arraycopy( |
| existingTypes, |
| 0, |
| existingTypes = new IType[existingTypeLength + 1], |
| 0, existingTypeLength); |
| existingTypes[existingTypeLength] = type; |
| typeMap.put(typeName, existingTypes); |
| } |
| } |
| } |
| } catch (ModelException e) { |
| // working copy doesn't exist -> ignore |
| } |
| |
| // add root of package fragment to cache |
| IProjectFragment root = (IProjectFragment) pkg.getParent(); |
| IPath pkgPath = pkg.getPath().removeFirstSegments( |
| root.getPath().segmentCount()); |
| String[] pkgName = pkgPath.segments(); |
| Object existing = this.scriptFolders.get(pkgName); |
| if (existing == null) { |
| this.scriptFolders.put(pkgName, root); |
| // cache whether each package and its including packages |
| // (see |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=119161) |
| // are actual packages |
| ProjectElementInfo.addNames(pkgName, this.isPackageCache); |
| } else { |
| if (existing instanceof IProjectFragment) { |
| if (!existing.equals(root)) |
| this.scriptFolders |
| .put(pkgName, new IProjectFragment[] { |
| (IProjectFragment) existing, root }); |
| } else { |
| IProjectFragment[] roots = (IProjectFragment[]) existing; |
| int rootLength = roots.length; |
| boolean containsRoot = false; |
| for (int j = 0; j < rootLength; j++) { |
| if (roots[j].equals(root)) { |
| containsRoot = true; |
| break; |
| } |
| } |
| if (containsRoot) { |
| System.arraycopy( |
| roots, |
| 0, |
| roots = new IProjectFragment[rootLength + 1], |
| 0, rootLength); |
| roots[rootLength] = root; |
| this.scriptFolders.put(pkgName, roots); |
| } |
| } |
| } |
| } |
| } |
| |
| this.rootToResolvedEntries = rootToResolvedEntries; |
| if (VERBOSE) { |
| Util.verbose(" -> spent: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| /** |
| * 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, |
| boolean isSourceType) { |
| return true; |
| } |
| |
| /** |
| * 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, IModelElementRequestor requestor) { |
| int count = this.projectFragments.length; |
| for (int i = 0; i < count; i++) { |
| if (requestor.isCanceled()) |
| return; |
| IProjectFragment root = this.projectFragments[i]; |
| IModelElement[] packages = null; |
| try { |
| packages = root.getChildren(); |
| } catch (ModelException 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, (IScriptFolder) packages[j], |
| partialMatch, acceptFlags, requestor); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the <code>ISourceModule</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>IScriptProject</code> this <code>NameLookup</code> was obtained |
| * from. |
| * <p> |
| * The name must be fully qualified (eg "java.lang.Object", |
| * "java.util.Hashtable$Entry") |
| */ |
| public ISourceModule findSourceModule(String qualifiedTypeName) { |
| String[] pkgName = CharOperation.NO_STRINGS; |
| String cuName = qualifiedTypeName; |
| |
| int index = qualifiedTypeName.lastIndexOf('.'); |
| if (index != -1) { |
| pkgName = Util.splitOn('.', qualifiedTypeName, 0, index); |
| cuName = qualifiedTypeName.substring(index + 1); |
| } |
| index = cuName.indexOf('$'); |
| if (index != -1) { |
| cuName = cuName.substring(0, index); |
| } |
| Object value = this.scriptFolders.get(pkgName); |
| if (value != null) { |
| if (value instanceof IProjectFragment) { |
| return findSourceModule(pkgName, cuName, |
| (IProjectFragment) value); |
| } else { |
| IProjectFragment[] roots = (IProjectFragment[]) value; |
| for (int i = 0; i < roots.length; i++) { |
| IProjectFragment root = roots[i]; |
| ISourceModule cu = findSourceModule(pkgName, cuName, root); |
| if (cu != null) |
| return cu; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private IPath toPath(String pkgName[]) { |
| IPath path = new Path(""); //$NON-NLS-1$ |
| for (int i = 0; i < pkgName.length; ++i) { |
| path = path.append(pkgName[i]); |
| } |
| return path; |
| } |
| |
| private ISourceModule findSourceModule(String[] pkgName, String cuName, |
| IProjectFragment root) { |
| if (!root.isArchive()) { |
| IScriptFolder pkg = root.getScriptFolder(toPath(pkgName)); |
| try { |
| ISourceModule[] cus = pkg.getSourceModules(); |
| for (int j = 0, length = cus.length; j < length; j++) { |
| ISourceModule cu = cus[j]; |
| if (Util.equalsIgnoreExtension(cu.getElementName(), cuName)) |
| return cu; |
| } |
| } catch (ModelException e) { |
| // pkg does not exist |
| // -> try next package |
| } |
| } |
| return null; |
| } |
| |
| private static boolean equals(String s1, String s2) { |
| return s1 == null ? s2 == null : s1.equals(s2); |
| } |
| |
| /** |
| * 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 buildpath of the <code>IScriptProject</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 IScriptFolder findScriptFolder(IPath path) { |
| if (!path.isAbsolute()) { |
| throw new IllegalArgumentException(Messages.path_mustBeAbsolute); |
| } |
| /* |
| * TODO (jerome) 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 |
| final boolean isFullPath = EnvironmentPathUtils.isFull(path); |
| for (int i = 0; i < this.projectFragments.length; i++) { |
| IProjectFragment root = this.projectFragments[i]; |
| if (!root.isExternal() || root.isBuiltin()) { |
| continue; |
| } |
| IPath rootPath = root.getPath(); |
| if (!isFullPath) { |
| rootPath = EnvironmentPathUtils.getLocalPath(rootPath); |
| } |
| int matchingCount = rootPath.matchingFirstSegments(path); |
| if (matchingCount != 0 |
| && equals(rootPath.getDevice(), path.getDevice())) { |
| final String name = path.removeFirstSegments(matchingCount) |
| .setDevice(null).makeRelative().toString(); |
| IModelElement[] list = null; |
| try { |
| list = root.getChildren(); |
| } catch (ModelException npe) { |
| continue; // the package fragment root is not present; |
| } |
| int elementCount = list.length; |
| for (int j = 0; j < elementCount; j++) { |
| IScriptFolder scriptFolder = (IScriptFolder) list[j]; |
| if (nameMatches(name, scriptFolder, false)) { |
| return scriptFolder; |
| } |
| } |
| } |
| } |
| } else { |
| IModelElement fromFactory = DLTKCore.create(possibleFragment); |
| if (fromFactory == null) { |
| return null; |
| } |
| switch (fromFactory.getElementType()) { |
| case IModelElement.SCRIPT_FOLDER: |
| return (IScriptFolder) fromFactory; |
| case IModelElement.SCRIPT_PROJECT: |
| // default package in a default root |
| ScriptProject project = (ScriptProject) fromFactory; |
| try { |
| IBuildpathEntry entry = project.getBuildpathEntryFor(path); |
| if (entry != null) { |
| IProjectFragment root = project |
| .getProjectFragment(project.getResource()); |
| Object defaultPkgRoot = this.scriptFolders |
| .get(CharOperation.NO_STRINGS); |
| if (defaultPkgRoot == null) { |
| return null; |
| } |
| if (defaultPkgRoot instanceof IProjectFragment |
| && defaultPkgRoot.equals(root)) |
| return root.getScriptFolder(Path.EMPTY); |
| else { |
| IProjectFragment[] roots = (IProjectFragment[]) defaultPkgRoot; |
| for (int i = 0; i < roots.length; i++) { |
| if (roots[i].equals(root)) { |
| return root.getScriptFolder(Path.EMPTY); |
| } |
| } |
| } |
| } |
| } catch (ModelException e) { |
| return null; |
| } |
| return null; |
| case IModelElement.PROJECT_FRAGMENT: |
| return ((IProjectFragment) fromFactory) |
| .getScriptFolder(Path.EMPTY); |
| } |
| } |
| 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 IScriptFolder[] findScriptFolders(String name, boolean partialMatch) { |
| if (partialMatch) { |
| String[] splittedName = Util.splitOn('.', name, 0, name.length()); |
| IScriptFolder[] oneFragment = null; |
| ArrayList pkgs = null; |
| Object[][] keys = this.scriptFolders.keyTable; |
| for (int i = 0, length = keys.length; i < length; i++) { |
| String[] pkgName = (String[]) keys[i]; |
| if (pkgName != null |
| && Util.startsWithIgnoreCase(pkgName, splittedName)) { |
| Object value = this.scriptFolders.valueTable[i]; |
| if (value instanceof IProjectFragment) { |
| IScriptFolder pkg = ((IProjectFragment) value) |
| .getScriptFolder(toPath(pkgName)); |
| if (oneFragment == null) { |
| oneFragment = new IScriptFolder[] { pkg }; |
| } else { |
| if (pkgs == null) { |
| pkgs = new ArrayList(); |
| pkgs.add(oneFragment[0]); |
| } |
| pkgs.add(pkg); |
| } |
| } else { |
| IProjectFragment[] roots = (IProjectFragment[]) value; |
| for (int j = 0, length2 = roots.length; j < length2; j++) { |
| IProjectFragment root = roots[j]; |
| IScriptFolder pkg = root |
| .getScriptFolder(toPath(pkgName)); |
| if (oneFragment == null) { |
| oneFragment = new IScriptFolder[] { pkg }; |
| } else { |
| if (pkgs == null) { |
| pkgs = new ArrayList(); |
| pkgs.add(oneFragment[0]); |
| } |
| pkgs.add(pkg); |
| } |
| } |
| } |
| } |
| } |
| if (pkgs == null) |
| return oneFragment; |
| int resultLength = pkgs.size(); |
| IScriptFolder[] result = new IScriptFolder[resultLength]; |
| pkgs.toArray(result); |
| return result; |
| } else { |
| String[] splittedName = Util.splitOn('.', name, 0, name.length()); |
| Object value = this.scriptFolders.get(splittedName); |
| if (value == null) |
| return null; |
| if (value instanceof IProjectFragment) { |
| return new IScriptFolder[] { ((IProjectFragment) value) |
| .getScriptFolder(toPath(splittedName)) }; |
| } else { |
| IProjectFragment[] roots = (IProjectFragment[]) value; |
| IScriptFolder[] result = new IScriptFolder[roots.length]; |
| for (int i = 0; i < roots.length; i++) { |
| result[i] = roots[i].getScriptFolder(toPath(splittedName)); |
| } |
| return result; |
| } |
| } |
| } |
| |
| /** |
| * Find type considering secondary types but without waiting for indexes. It |
| * means that secondary types may be not found under certain |
| * circumstances... |
| * |
| * @see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=118789" |
| */ |
| public Answer findType(String typeName, String packageName, |
| boolean partialMatch, int acceptFlags, boolean checkRestrictions) { |
| return findType(typeName, packageName, partialMatch, acceptFlags, |
| true/* consider secondary types */, false/* |
| * do NOT wait for |
| * indexes |
| */, checkRestrictions, |
| null); |
| } |
| |
| /** |
| * Find type. Considering secondary types and waiting for indexes depends on |
| * given corresponding parameters. |
| */ |
| public Answer findType(String typeName, String packageName, |
| boolean partialMatch, int acceptFlags, |
| boolean considerSecondaryTypes, boolean waitForIndexes, |
| boolean checkRestrictions, IProgressMonitor monitor) { |
| if (packageName == null || packageName.length() == 0) { |
| packageName = IScriptFolder.DEFAULT_FOLDER_NAME; |
| } else if (typeName.length() > 0 |
| && Character.isLowerCase(typeName.charAt(0))) { |
| // see if this is a known package and not a type |
| if (findScriptFolders(packageName + "." + typeName, false) != null)return null; //$NON-NLS-1$ |
| } |
| |
| // Look for concerned package fragments |
| ModelElementRequestor elementRequestor = new ModelElementRequestor(); |
| seekScriptFolders(packageName, false, elementRequestor); |
| IScriptFolder[] packages = elementRequestor.getScriptFolders(); |
| |
| // Try to find type in package fragments list |
| IType type = null; |
| int length = packages.length; |
| HashSet projects = null; |
| IScriptProject scriptProject = null; |
| Answer suggestedAnswer = null; |
| for (int i = 0; i < length; i++) { |
| type = findType(typeName, packages[i], partialMatch, acceptFlags); |
| if (type != null) { |
| AccessRestriction accessRestriction = null; |
| if (checkRestrictions) { |
| accessRestriction = getViolatedRestriction(typeName, |
| packageName, type, accessRestriction); |
| } |
| Answer answer = new Answer(type, accessRestriction); |
| if (!answer.ignoreIfBetter()) { |
| if (answer.isBetter(suggestedAnswer)) |
| return answer; |
| } else if (answer.isBetter(suggestedAnswer)) |
| // remember suggestion and keep looking |
| suggestedAnswer = answer; |
| } else if (suggestedAnswer == null && considerSecondaryTypes) { |
| if (scriptProject == null) { |
| scriptProject = packages[i].getScriptProject(); |
| } else if (projects == null) { |
| if (!scriptProject.equals(packages[i].getScriptProject())) { |
| projects = new HashSet(3); |
| projects.add(scriptProject); |
| projects.add(packages[i].getScriptProject()); |
| } |
| } else { |
| projects.add(packages[i].getScriptProject()); |
| } |
| } |
| } |
| if (suggestedAnswer != null) |
| // no better answer was found |
| return suggestedAnswer; |
| |
| return type == null ? null : new Answer(type, null); |
| } |
| |
| private AccessRestriction getViolatedRestriction(String typeName, |
| String packageName, IType type, AccessRestriction accessRestriction) { |
| IProjectFragment root = (IProjectFragment) type |
| .getAncestor(IModelElement.PROJECT_FRAGMENT); |
| BuildpathEntry entry = (BuildpathEntry) this.rootToResolvedEntries |
| .get(root); |
| if (entry != null) { // reverse map always contains resolved CP entry |
| AccessRuleSet accessRuleSet = entry.getAccessRuleSet(); |
| if (accessRuleSet != null) { |
| // TODO (philippe) improve char[] <-> String conversions to |
| // avoid performing them on the fly |
| char[][] packageChars = CharOperation.splitOn('.', |
| packageName.toCharArray()); |
| char[] typeChars = typeName.toCharArray(); |
| accessRestriction = accessRuleSet |
| .getViolatedRestriction(CharOperation.concatWith( |
| packageChars, typeChars, '/')); |
| } |
| } |
| return accessRestriction; |
| } |
| |
| /** |
| * 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 Script 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. |
| * @param considerSecondaryTypes |
| * flag to know whether secondary types has to be considered |
| * during the search |
| * |
| * @see #ACCEPT_CLASSES |
| * @see #ACCEPT_INTERFACES |
| * @see #ACCEPT_ENUMS |
| * @see #ACCEPT_ANNOTATIONS |
| */ |
| public IType findType(String name, IScriptFolder pkg, boolean partialMatch, |
| int acceptFlags, boolean considerSecondaryTypes) { |
| IType type = findType(name, pkg, partialMatch, acceptFlags); |
| return type; |
| } |
| |
| /** |
| * 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 Script project from which this name lookup was |
| * obtained. <br> |
| * Note that this method does not find secondary types. <br> |
| * |
| * @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 |
| * @see #ACCEPT_ENUMS |
| * @see #ACCEPT_ANNOTATIONS |
| */ |
| public IType findType(String name, IScriptFolder 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); |
| return typeRequestor.getType(); |
| } |
| |
| /** |
| * Returns the type specified by the qualified name, or <code>null</code> if |
| * none exist. The domain of the search is bounded by the Script 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 |
| * @see #ACCEPT_ENUMS |
| * @see #ACCEPT_ANNOTATIONS |
| */ |
| public IType findType(String name, boolean partialMatch, int acceptFlags) { |
| NameLookup.Answer answer = findType(name, partialMatch, acceptFlags, |
| false/* don't check restrictions */); |
| return answer == null ? null : answer.type; |
| } |
| |
| public Answer findType(String name, boolean partialMatch, int acceptFlags, |
| boolean checkRestrictions) { |
| return findType(name, partialMatch, acceptFlags, true/* |
| * consider |
| * secondary types |
| */, true/* |
| * wait for |
| * indexes |
| */, |
| checkRestrictions, null); |
| } |
| |
| public Answer findType(String name, boolean partialMatch, int acceptFlags, |
| boolean considerSecondaryTypes, boolean waitForIndexes, |
| boolean checkRestrictions, IProgressMonitor monitor) { |
| int index = name.lastIndexOf('.'); |
| String className = null, packageName = null; |
| if (index == -1) { |
| packageName = IScriptFolder.DEFAULT_FOLDER_NAME; |
| className = name; |
| } else { |
| packageName = name.substring(0, index); |
| className = name.substring(index + 1); |
| } |
| return findType(className, packageName, partialMatch, acceptFlags, |
| considerSecondaryTypes, waitForIndexes, checkRestrictions, |
| monitor); |
| } |
| |
| private IType getMemberType(IType type, String name, int dot) { |
| while (dot != -1) { |
| int start = dot + 1; |
| dot = name.indexOf('.', start); |
| String typeName = name.substring(start, dot == -1 ? name.length() |
| : dot); |
| type = type.getType(typeName); |
| } |
| return type; |
| } |
| |
| public boolean isPackage(String[] pkgName) { |
| return this.isPackageCache.get(pkgName) != null; |
| } |
| |
| /** |
| * 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, IModelElement 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); |
| } |
| } |
| |
| /** |
| * Returns true if the given cu'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, ISourceModule cu, |
| boolean partialMatch) { |
| if (partialMatch) { |
| // partial matches are used in completion mode, thus case |
| // insensitive mode |
| return cu.getElementName().toLowerCase().startsWith(searchName); |
| } else { |
| return Util.equalsIgnoreExtension(cu.getElementName(), 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>IScriptProject</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 seekScriptFolders(String name, boolean partialMatch, |
| IModelElementRequestor requestor) { |
| /* |
| * if (VERBOSE) { Util.verbose(" SEEKING PACKAGE FRAGMENTS"); |
| * //$NON-NLS-1$ Util.verbose(" -> name: " + name); //$NON-NLS-1$ |
| * Util.verbose(" -> partial match:" + partialMatch); //$NON-NLS-1$ } |
| */if (partialMatch) { |
| String[] splittedName = Util.splitOn('.', name, 0, name.length()); |
| Object[][] keys = this.scriptFolders.keyTable; |
| for (int i = 0, length = keys.length; i < length; i++) { |
| if (requestor.isCanceled()) |
| return; |
| String[] pkgName = (String[]) keys[i]; |
| if (pkgName != null |
| && Util.startsWithIgnoreCase(pkgName, splittedName)) { |
| Object value = this.scriptFolders.valueTable[i]; |
| if (value instanceof IProjectFragment) { |
| IProjectFragment root = (IProjectFragment) value; |
| requestor.acceptScriptFolder(root |
| .getScriptFolder(toPath(pkgName))); |
| } else { |
| IProjectFragment[] roots = (IProjectFragment[]) value; |
| for (int j = 0, length2 = roots.length; j < length2; j++) { |
| if (requestor.isCanceled()) |
| return; |
| IProjectFragment root = roots[j]; |
| requestor.acceptScriptFolder(root |
| .getScriptFolder(toPath(pkgName))); |
| } |
| } |
| } |
| } |
| } else { |
| String[] splittedName = Util.splitOn('.', name, 0, name.length()); |
| Object value = this.scriptFolders.get(splittedName); |
| if (value instanceof IProjectFragment) { |
| requestor.acceptScriptFolder(((IProjectFragment) value) |
| .getScriptFolder(toPath(splittedName))); |
| } else { |
| IProjectFragment[] roots = (IProjectFragment[]) value; |
| if (roots != null) { |
| for (int i = 0, length = roots.length; i < length; i++) { |
| if (requestor.isCanceled()) |
| return; |
| IProjectFragment root = roots[i]; |
| requestor.acceptScriptFolder(root |
| .getScriptFolder(toPath(splittedName))); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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 |
| * @see #ACCEPT_ENUMS |
| * @see #ACCEPT_ANNOTATIONS |
| */ |
| public void seekTypes(String name, IScriptFolder pkg, boolean partialMatch, |
| int acceptFlags, IModelElementRequestor requestor) { |
| /* |
| * if (VERBOSE) { Util.verbose(" SEEKING TYPES"); //$NON-NLS-1$ |
| * Util.verbose(" -> name: " + name); //$NON-NLS-1$ |
| * Util.verbose(" -> pkg: " + ((ModelElement) |
| * pkg).toStringWithAncestors()); //$NON-NLS-1$ |
| * Util.verbose(" -> partial match:" + partialMatch); //$NON-NLS-1$ } |
| */ |
| String matchName = partialMatch ? name.toLowerCase() : name; |
| if (pkg == null) { |
| findAllTypes(matchName, partialMatch, acceptFlags, requestor); |
| return; |
| } |
| IProjectFragment root = (IProjectFragment) pkg.getParent(); |
| try { |
| |
| // look in working copies first |
| int firstDot = -1; |
| String topLevelTypeName = null; |
| int packageFlavor = root.getKind(); |
| if (this.typesInWorkingCopies != null |
| || packageFlavor == IProjectFragment.K_SOURCE) { |
| firstDot = matchName.indexOf('.'); |
| if (!partialMatch) |
| topLevelTypeName = firstDot == -1 ? matchName : matchName |
| .substring(0, firstDot); |
| } |
| if (this.typesInWorkingCopies != null) { |
| if (seekTypesInWorkingCopies(matchName, pkg, firstDot, |
| partialMatch, topLevelTypeName, acceptFlags, requestor)) |
| return; |
| } |
| |
| // look in model |
| switch (packageFlavor) { |
| case IProjectFragment.K_SOURCE: |
| seekTypesInSourcePackage(matchName, pkg, firstDot, |
| partialMatch, topLevelTypeName, acceptFlags, requestor); |
| break; |
| default: |
| return; |
| } |
| } catch (ModelException e) { |
| return; |
| } |
| } |
| |
| /** |
| * Performs type search in a source package. |
| */ |
| protected void seekTypesInSourcePackage(String name, IScriptFolder pkg, |
| int firstDot, boolean partialMatch, String topLevelTypeName, |
| int acceptFlags, IModelElementRequestor requestor) { |
| |
| long start = -1; |
| if (VERBOSE) |
| start = System.currentTimeMillis(); |
| try { |
| if (!partialMatch) { |
| try { |
| IModelElement[] compilationUnits = pkg.getChildren(); |
| for (int i = 0, length = compilationUnits.length; i < length; i++) { |
| if (requestor.isCanceled()) |
| return; |
| IModelElement cu = compilationUnits[i]; |
| String cuName = cu.getElementName(); |
| int lastDot = cuName.lastIndexOf('.'); |
| if (lastDot != topLevelTypeName.length() |
| || !topLevelTypeName.regionMatches(0, cuName, |
| 0, lastDot)) |
| continue; |
| IType type = ((ISourceModule) cu) |
| .getType(topLevelTypeName); |
| type = getMemberType(type, name, firstDot); |
| if (acceptType(type, acceptFlags, true/* a source type */)) { // accept |
| // type |
| // checks |
| // for |
| // existence |
| requestor.acceptType(type); |
| break; // since an exact match was requested, no |
| // other matching type can exist |
| } |
| } |
| } catch (ModelException e) { |
| // package doesn't exist -> ignore |
| } |
| } else { |
| try { |
| String cuPrefix = firstDot == -1 ? name : name.substring(0, |
| firstDot); |
| IModelElement[] compilationUnits = pkg.getChildren(); |
| for (int i = 0, length = compilationUnits.length; i < length; i++) { |
| if (requestor.isCanceled()) |
| return; |
| IModelElement cu = compilationUnits[i]; |
| if (!cu.getElementName().toLowerCase() |
| .startsWith(cuPrefix)) |
| continue; |
| try { |
| IType[] types = ((ISourceModule) cu).getTypes(); |
| for (int j = 0, typeLength = types.length; j < typeLength; j++) |
| seekTypesInTopLevelType(name, firstDot, |
| types[j], requestor, acceptFlags); |
| } catch (ModelException e) { |
| // cu doesn't exist -> ignore |
| } |
| } |
| } catch (ModelException e) { |
| // package doesn't exist -> ignore |
| } |
| } |
| } finally { |
| if (VERBOSE) |
| this.timeSpentInSeekTypesInSourcePackage += System |
| .currentTimeMillis() - start; |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| protected boolean seekTypesInType(String prefix, int firstDot, IType type, |
| IModelElementRequestor requestor, int acceptFlags) { |
| IType[] types = null; |
| try { |
| types = type.getTypes(); |
| } catch (ModelException npe) { |
| return false; // the enclosing type is not present |
| } |
| int length = types.length; |
| if (length == 0) |
| return false; |
| |
| String memberPrefix = prefix; |
| boolean isMemberTypePrefix = false; |
| if (firstDot != -1) { |
| memberPrefix = prefix.substring(0, firstDot); |
| isMemberTypePrefix = true; |
| } |
| for (int i = 0; i < length; i++) { |
| if (requestor.isCanceled()) |
| return false; |
| IType memberType = types[i]; |
| if (memberType.getElementName().toLowerCase() |
| .startsWith(memberPrefix)) |
| if (isMemberTypePrefix) { |
| String subPrefix = prefix.substring(firstDot + 1, |
| prefix.length()); |
| return seekTypesInType(subPrefix, subPrefix.indexOf('.'), |
| memberType, requestor, acceptFlags); |
| } else { |
| if (acceptType(memberType, acceptFlags, true/* a source type */)) { |
| requestor.acceptMemberType(memberType); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| protected boolean seekTypesInTopLevelType(String prefix, int firstDot, |
| IType topLevelType, IModelElementRequestor requestor, |
| int acceptFlags) { |
| if (!topLevelType.getElementName().toLowerCase().startsWith(prefix)) |
| return false; |
| if (firstDot == -1) { |
| if (acceptType(topLevelType, acceptFlags, true/* a source type */)) { |
| requestor.acceptType(topLevelType); |
| return true; |
| } |
| } else { |
| return seekTypesInType(prefix, firstDot, topLevelType, requestor, |
| acceptFlags); |
| } |
| return false; |
| } |
| |
| /* |
| * Seeks the type with the given name in the map of types with precedence |
| * (coming from working copies) Return whether a type has been found. |
| */ |
| protected boolean seekTypesInWorkingCopies(String name, IScriptFolder pkg, |
| int firstDot, boolean partialMatch, String topLevelTypeName, |
| int acceptFlags, IModelElementRequestor requestor) { |
| |
| if (!partialMatch) { |
| HashMap typeMap = (HashMap) (this.typesInWorkingCopies == null ? null |
| : this.typesInWorkingCopies.get(pkg)); |
| if (typeMap != null) { |
| Object object = typeMap.get(topLevelTypeName); |
| if (object instanceof IType) { |
| IType type = getMemberType((IType) object, name, firstDot); |
| if (acceptType(type, acceptFlags, true/* a source type */)) { |
| requestor.acceptType(type); |
| return true; // don't continue with compilation unit |
| } |
| } else if (object instanceof IType[]) { |
| if (object == NO_TYPES) |
| return true; // all types where deleted -> type is |
| // hidden |
| IType[] topLevelTypes = (IType[]) object; |
| for (int i = 0, length = topLevelTypes.length; i < length; i++) { |
| if (requestor.isCanceled()) |
| return false; |
| IType type = getMemberType(topLevelTypes[i], name, |
| firstDot); |
| if (acceptType(type, acceptFlags, true/* a source type */)) { |
| requestor.acceptType(type); |
| return true; // return the first one |
| } |
| } |
| } |
| } |
| } else { |
| HashMap typeMap = (HashMap) (this.typesInWorkingCopies == null ? null |
| : this.typesInWorkingCopies.get(pkg)); |
| if (typeMap != null) { |
| Iterator iterator = typeMap.values().iterator(); |
| while (iterator.hasNext()) { |
| if (requestor.isCanceled()) |
| return false; |
| Object object = iterator.next(); |
| if (object instanceof IType) { |
| seekTypesInTopLevelType(name, firstDot, (IType) object, |
| requestor, acceptFlags); |
| } else if (object instanceof IType[]) { |
| IType[] topLevelTypes = (IType[]) object; |
| for (int i = 0, length = topLevelTypes.length; i < length; i++) |
| seekTypesInTopLevelType(name, firstDot, |
| topLevelTypes[i], requestor, acceptFlags); |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| } |