blob: 1d62827b27c906ba2b2407df50451b8855f7ffaa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
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;
}
}