blob: d3603526aca03cd32de67bda8050038913f399eb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - contribution for bug 337868 - [compiler][model] incomplete support for package-info.java when using SearchableEnvironment
*******************************************************************************/
package org.eclipse.jdt.internal.core;
import java.io.File;
import java.util.*;
import java.util.function.Function;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IInitializer;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.IModule;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.parser.ScannerHelper;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToInt;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.AbstractModule.AutoModule;
import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
/**
* A <code>NameLookup</code> provides name resolution within a Java project.
* The name lookup facility uses the project's classpath to prioritize the
* order in which package fragments are searched when resolving a name.
*
* <p>Name lookup only returns a handle when the named element actually
* exists in the model; otherwise <code>null</code> is returned.
*
* <p>There are two logical sets of methods within this interface. Methods
* which start with <code>find*</code> are intended to be convenience methods for quickly
* finding an element within another element; for instance, for finding a class within a
* package. The other set of methods all begin with <code>seek*</code>. These methods
* do comprehensive searches of the <code>IJavaProject</code> returning hits
* in real time through an <code>IJavaElementRequestor</code>.
*
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class NameLookup implements SuffixConstants {
private static IModuleDescription NO_MODULE = new SourceModule(null, "Not a module") { /* empty */ }; //$NON-NLS-1$
public static class Answer {
public IType type;
public IModuleDescription module;
AccessRestriction restriction;
IClasspathEntry entry;
Answer(IType type, AccessRestriction restriction, IClasspathEntry entry) {
this(type, restriction, entry, null);
}
Answer(IType type, AccessRestriction restriction, IClasspathEntry entry, IModuleDescription module) {
this.type = type;
this.restriction = restriction;
this.entry = entry;
this.module = module;
}
Answer(IModuleDescription module) {
this.module = module;
this.restriction = null;
}
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();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(this.type.toString());
builder.append("from ") //$NON-NLS-1$
.append(this.module);
return builder.toString();
}
}
private class Selector implements IJavaElementRequestor {
public List<IPackageFragment> pkgFragments;
public Selector(String moduleName) {
this.pkgFragments = new ArrayList<>();
}
@Override
public void acceptField(IField field) {
// do nothing
}
@Override
public void acceptInitializer(IInitializer initializer) {
// do nothing
}
@Override
public void acceptMemberType(IType type) {
// do nothing
}
@Override
public void acceptMethod(IMethod method) {
// do nothing
}
@Override
public void acceptPackageFragment(IPackageFragment packageFragment) {
this.pkgFragments.add(packageFragment);
}
@Override
public void acceptType(IType type) {
// do nothing
}
@Override
public void acceptModule(IModuleDescription module) {
// do nothing
}
@Override
public boolean isCanceled() {
// TODO Auto-generated method stub
return false;
}
}
// TODO (jerome) suppress the accept flags (qualified name is sufficient to find a type)
/**
* Accept flag for specifying classes.
*/
public static final int ACCEPT_CLASSES = ASTNode.Bit2;
/**
* Accept flag for specifying interfaces.
*/
public static final int ACCEPT_INTERFACES = ASTNode.Bit3;
/**
* Accept flag for specifying enums.
*/
public static final int ACCEPT_ENUMS = ASTNode.Bit4;
/**
* Accept flag for specifying annotations.
*/
public static final int ACCEPT_ANNOTATIONS = ASTNode.Bit5;
/*
* Accept flag for all kinds of types
*/
public static final int ACCEPT_ALL = ACCEPT_CLASSES | ACCEPT_INTERFACES | ACCEPT_ENUMS | ACCEPT_ANNOTATIONS;
public static boolean VERBOSE = false;
private static final IType[] NO_TYPES = {};
/**
* The <code>IPackageFragmentRoot</code>'s associated
* with the classpath of this NameLookup facility's
* project.
*/
protected IPackageFragmentRoot[] packageFragmentRoots;
/**
* Table that maps package names to lists of package 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
* classpath.
* Note if the list is of size 1, then the IPackageFragmentRoot object
* replaces the array.
*/
protected HashtableOfArrayToObject packageFragments;
/**
* Reverse map from root path to corresponding resolved CP entry
* (so as to be able to figure inclusion/exclusion rules)
*/
protected Map<IPackageFragmentRoot,IClasspathEntry> rootToResolvedEntries;
protected Map<IPackageFragmentRoot,IModuleDescription> rootToModule;
/**
* 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;
private JavaProject rootProject;
public NameLookup(
JavaProject rootProject, IPackageFragmentRoot[] packageFragmentRoots,
HashtableOfArrayToObject packageFragments,
ICompilationUnit[] workingCopies,
Map rootToResolvedEntries) {
this.rootProject = rootProject;
long start = -1;
if (VERBOSE) {
Util.verbose(" BUILDING NameLoopkup"); //$NON-NLS-1$
Util.verbose(" -> pkg roots size: " + (packageFragmentRoots == null ? 0 : packageFragmentRoots.length)); //$NON-NLS-1$
Util.verbose(" -> pkgs size: " + (packageFragments == null ? 0 : packageFragments.size())); //$NON-NLS-1$
Util.verbose(" -> working copy size: " + (workingCopies == null ? 0 : workingCopies.length)); //$NON-NLS-1$
start = System.currentTimeMillis();
}
this.rootToModule = new HashMap<>();
this.packageFragmentRoots = packageFragmentRoots;
if (workingCopies == null) {
this.packageFragments = packageFragments;
} else {
// clone tables as we're adding packages from working copies
try {
this.packageFragments = (HashtableOfArrayToObject) packageFragments.clone();
} catch (CloneNotSupportedException e1) {
// ignore (implementation of HashtableOfArrayToObject supports cloning)
}
this.typesInWorkingCopies = new HashMap();
HashtableOfObjectToInt rootPositions = new HashtableOfObjectToInt();
for (int i = 0, length = packageFragmentRoots.length; i < length; i++) {
rootPositions.put(packageFragmentRoots[i], i);
}
for (int i = 0, length = workingCopies.length; i < length; i++) {
ICompilationUnit workingCopy = workingCopies[i];
PackageFragment pkg = (PackageFragment) workingCopy.getParent();
IPackageFragmentRoot root = (IPackageFragmentRoot) pkg.getParent();
int rootPosition = rootPositions.get(root);
if (rootPosition == -1)
continue; // working copy is not visible from this project (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=169970)
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 = Util.getNameWithoutJavaLikeExtension(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 (JavaModelException e) {
// working copy doesn't exist -> ignore
}
// add root of package fragment to cache
String[] pkgName = pkg.names;
Object existing = this.packageFragments.get(pkgName);
if (existing == null || existing == JavaProjectElementInfo.NO_ROOTS) {
this.packageFragments.put(pkgName, root);
// ensure super packages (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=119161)
// are also in the map
JavaProjectElementInfo.addSuperPackageNames(pkgName, this.packageFragments);
} else {
if (existing instanceof PackageFragmentRoot) {
int exisitingPosition = rootPositions.get(existing);
if (rootPosition != exisitingPosition) { // if not equal
this.packageFragments.put(
pkgName,
exisitingPosition < rootPosition ?
new IPackageFragmentRoot[] {(PackageFragmentRoot) existing, root} :
new IPackageFragmentRoot[] {root, (PackageFragmentRoot) existing});
}
} else {
// insert root in the existing list
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) existing;
int rootLength = roots.length;
int insertionIndex = 0;
for (int j = 0; j < rootLength; j++) {
int existingPosition = rootPositions.get(roots[j]);
if (rootPosition > existingPosition) {
// root is after this index
insertionIndex = j;
} else if (rootPosition == existingPosition) {
// root already in the existing list
insertionIndex = -1;
break;
} else if (rootPosition < existingPosition) {
// root is before this index (thus it is at the insertion index)
break;
}
}
if (insertionIndex != -1) {
IPackageFragmentRoot[] newRoots = new IPackageFragmentRoot[rootLength+1];
System.arraycopy(roots, 0, newRoots, 0, insertionIndex);
newRoots[insertionIndex] = root;
System.arraycopy(roots, insertionIndex, newRoots, insertionIndex+1, rootLength-insertionIndex);
this.packageFragments.put(pkgName, newRoots);
}
}
}
}
}
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) {
if (acceptFlags == 0 || acceptFlags == ACCEPT_ALL)
return true; // no flags or all flags, always accepted
try {
int kind = isSourceType
? TypeDeclaration.kind(((SourceTypeElementInfo) ((SourceType) type).getElementInfo()).getModifiers())
: TypeDeclaration.kind(((IBinaryType) ((BinaryType) type).getElementInfo()).getModifiers());
switch (kind) {
case TypeDeclaration.CLASS_DECL :
return (acceptFlags & ACCEPT_CLASSES) != 0;
case TypeDeclaration.INTERFACE_DECL :
return (acceptFlags & ACCEPT_INTERFACES) != 0;
case TypeDeclaration.ENUM_DECL :
return (acceptFlags & ACCEPT_ENUMS) != 0;
default:
//case IGenericType.ANNOTATION_TYPE :
return (acceptFlags & ACCEPT_ANNOTATIONS) != 0;
}
} catch (JavaModelException npe) {
return false; // the class is not present, do not accept.
}
}
/**
* Finds every type in the project whose simple name matches
* the prefix, informing the requestor of each hit. The requestor
* is polled for cancellation at regular intervals.
*
* <p>The <code>partialMatch</code> argument indicates partial matches
* should be considered.
*/
private void findAllTypes(String prefix, boolean partialMatch, int acceptFlags, IJavaElementRequestor requestor) {
int count= this.packageFragmentRoots.length;
for (int i= 0; i < count; i++) {
if (requestor.isCanceled())
return;
IPackageFragmentRoot root= this.packageFragmentRoots[i];
IJavaElement[] packages= null;
try {
packages= root.getChildren();
} catch (JavaModelException npe) {
continue; // the root is not present, continue;
}
if (packages != null) {
for (int j= 0, packageCount= packages.length; j < packageCount; j++) {
if (requestor.isCanceled())
return;
seekTypes(prefix, (IPackageFragment) packages[j], partialMatch, acceptFlags, requestor);
}
}
}
}
/**
* Returns the <code>ICompilationUnit</code> which defines the type
* named <code>qualifiedTypeName</code>, or <code>null</code> if
* none exists. The domain of the search is bounded by the classpath
* of the <code>IJavaProject</code> this <code>NameLookup</code> was
* obtained from.
* <p>
* The name must be fully qualified (eg "java.lang.Object", "java.util.Hashtable$Entry")
*/
public ICompilationUnit findCompilationUnit(String qualifiedTypeName) {
String[] pkgName = 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);
}
int pkgIndex = this.packageFragments.getIndex(pkgName);
if (pkgIndex != -1) {
Object value = this.packageFragments.valueTable[pkgIndex];
// reuse existing String[]
pkgName = (String[]) this.packageFragments.keyTable[pkgIndex];
if (value instanceof PackageFragmentRoot) {
return findCompilationUnit(pkgName, cuName, (PackageFragmentRoot) value);
} else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) value;
for (int i= 0; i < roots.length; i++) {
PackageFragmentRoot root= (PackageFragmentRoot) roots[i];
ICompilationUnit cu = findCompilationUnit(pkgName, cuName, root);
if (cu != null)
return cu;
}
}
}
return null;
}
private ICompilationUnit findCompilationUnit(String[] pkgName, String cuName, PackageFragmentRoot root) {
if (!root.isArchive()) {
IPackageFragment pkg = root.getPackageFragment(pkgName);
try {
ICompilationUnit[] cus = pkg.getCompilationUnits();
for (int j = 0, length = cus.length; j < length; j++) {
ICompilationUnit cu = cus[j];
if (Util.equalsIgnoreJavaLikeExtension(cu.getElementName(), cuName))
return cu;
}
} catch (JavaModelException e) {
// pkg does not exist
// -> try next package
}
}
return null;
}
/**
* Returns the package fragment whose path matches the given
* (absolute) path, or <code>null</code> if none exist. The domain of
* the search is bounded by the classpath of the <code>IJavaProject</code>
* this <code>NameLookup</code> was obtained from.
* The path can be:
* - internal to the workbench: "/Project/src"
* - external to the workbench: "c:/jdk/classes.zip/java/lang"
*/
public IPackageFragment findPackageFragment(IPath path) {
if (!path.isAbsolute()) {
throw new IllegalArgumentException(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
for (int i = 0; i < this.packageFragmentRoots.length; i++) {
IPackageFragmentRoot root = this.packageFragmentRoots[i];
if (!root.isExternal()) {
continue;
}
IPath rootPath = root.getPath();
if (rootPath.isPrefixOf(path)) {
String name = path.toOSString();
// + 1 is for the File.separatorChar
name = name.substring(rootPath.toOSString().length() + 1, name.length());
name = name.replace(File.separatorChar, '.');
IJavaElement[] list = null;
try {
list = root.getChildren();
} catch (JavaModelException npe) {
continue; // the package fragment root is not present;
}
int elementCount = list.length;
for (int j = 0; j < elementCount; j++) {
IPackageFragment packageFragment = (IPackageFragment) list[j];
if (nameMatches(name, packageFragment, false)) {
return packageFragment;
}
}
}
}
} else {
IJavaElement fromFactory = JavaCore.create(possibleFragment);
if (fromFactory == null) {
return null;
}
switch (fromFactory.getElementType()) {
case IJavaElement.PACKAGE_FRAGMENT:
return (IPackageFragment) fromFactory;
case IJavaElement.JAVA_PROJECT:
// default package in a default root
JavaProject project = (JavaProject) fromFactory;
try {
IClasspathEntry entry = project.getClasspathEntryFor(path);
if (entry != null) {
IPackageFragmentRoot root =
project.getPackageFragmentRoot(project.getResource());
Object defaultPkgRoot = this.packageFragments.get(CharOperation.NO_STRINGS);
if (defaultPkgRoot == null) {
return null;
}
if (defaultPkgRoot instanceof PackageFragmentRoot && defaultPkgRoot.equals(root))
return ((PackageFragmentRoot) root).getPackageFragment(CharOperation.NO_STRINGS);
else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) defaultPkgRoot;
for (int i = 0; i < roots.length; i++) {
if (roots[i].equals(root)) {
return ((PackageFragmentRoot) root).getPackageFragment(CharOperation.NO_STRINGS);
}
}
}
}
} catch (JavaModelException e) {
return null;
}
return null;
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
return ((PackageFragmentRoot)fromFactory).getPackageFragment(CharOperation.NO_STRINGS);
}
}
return null;
}
/**
* Returns the package fragments whose name matches the given
* (qualified) name, or <code>null</code> if none exist.
*
* The name can be:
* <ul>
* <li>empty: ""</li>
* <li>qualified: "pack.pack1.pack2"</li>
* </ul>
* @param partialMatch partial name matches qualify when <code>true</code>,
* only exact name matches qualify when <code>false</code>
*/
public IPackageFragment[] findPackageFragments(String name, boolean partialMatch) {
return findPackageFragments(name, partialMatch, false);
}
/**
* Returns the package fragments whose name matches the given
* (qualified) name or pattern, or <code>null</code> if none exist.
*
* The name can be:
* <ul>
* <li>empty: ""</li>
* <li>qualified: "pack.pack1.pack2"</li>
* <li>a pattern: "pack.*.util"</li>
* </ul>
* @param partialMatch partial name matches qualify when <code>true</code>,
* @param patternMatch <code>true</code> when the given name might be a pattern,
* <code>false</code> otherwise.
*/
public IPackageFragment[] findPackageFragments(String name, boolean partialMatch, boolean patternMatch) {
boolean isStarPattern = name.equals("*"); //$NON-NLS-1$
boolean hasPatternChars = isStarPattern || (patternMatch && (name.indexOf('*') >= 0 || name.indexOf('?') >= 0));
if (partialMatch || hasPatternChars) {
String[] splittedName = Util.splitOn('.', name, 0, name.length());
IPackageFragment[] oneFragment = null;
ArrayList pkgs = null;
char[] lowercaseName = hasPatternChars && !isStarPattern ? name.toLowerCase().toCharArray() : null;
Object[][] keys = this.packageFragments.keyTable;
for (int i = 0, length = keys.length; i < length; i++) {
String[] pkgName = (String[]) keys[i];
if (pkgName != null) {
boolean match = isStarPattern || (hasPatternChars
? CharOperation.match(lowercaseName, Util.concatCompoundNameToCharArray(pkgName), false)
: Util.startsWithIgnoreCase(pkgName, splittedName, partialMatch));
if (match) {
Object value = this.packageFragments.valueTable[i];
if (value instanceof PackageFragmentRoot) {
IPackageFragment pkg = ((PackageFragmentRoot) value).getPackageFragment(pkgName);
if (oneFragment == null) {
oneFragment = new IPackageFragment[] {pkg};
} else {
if (pkgs == null) {
pkgs = new ArrayList();
pkgs.add(oneFragment[0]);
}
pkgs.add(pkg);
}
} else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) value;
for (int j = 0, length2 = roots.length; j < length2; j++) {
PackageFragmentRoot root = (PackageFragmentRoot) roots[j];
IPackageFragment pkg = root.getPackageFragment(pkgName);
if (oneFragment == null) {
oneFragment = new IPackageFragment[] {pkg};
} else {
if (pkgs == null) {
pkgs = new ArrayList();
pkgs.add(oneFragment[0]);
}
pkgs.add(pkg);
}
}
}
}
}
}
if (pkgs == null) return oneFragment;
int resultLength = pkgs.size();
IPackageFragment[] result = new IPackageFragment[resultLength];
pkgs.toArray(result);
return result;
} else {
String[] splittedName = Util.splitOn('.', name, 0, name.length());
int pkgIndex = this.packageFragments.getIndex(splittedName);
if (pkgIndex == -1)
return null;
Object value = this.packageFragments.valueTable[pkgIndex];
// reuse existing String[]
String[] pkgName = (String[]) this.packageFragments.keyTable[pkgIndex];
if (value instanceof PackageFragmentRoot) {
return new IPackageFragment[] {((PackageFragmentRoot) value).getPackageFragment(pkgName)};
} else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) value;
IPackageFragment[] result = new IPackageFragment[roots.length];
for (int i= 0; i < roots.length; i++) {
result[i] = ((PackageFragmentRoot) roots[i]).getPackageFragment(pkgName);
}
return result;
}
}
}
/*
* Find secondary type for a project.
*/
private IType findSecondaryType(String packageName, String typeName, IJavaProject project, boolean waitForIndexes, IProgressMonitor monitor) {
JavaModelManager manager = JavaModelManager.getJavaModelManager();
try {
IJavaProject javaProject = project;
Map<String, Map<String, IType>> secondaryTypePaths = manager.secondaryTypes(javaProject, waitForIndexes, monitor);
if (secondaryTypePaths.size() > 0) {
Map<String, IType> types = secondaryTypePaths.get(packageName==null?"":packageName); //$NON-NLS-1$
if (types != null && types.size() > 0) {
IType type = types.get(typeName);
if (type != null) {
if (JavaModelManager.VERBOSE) {
Util.verbose("NameLookup FIND SECONDARY TYPES:"); //$NON-NLS-1$
Util.verbose(" -> pkg name: " + packageName); //$NON-NLS-1$
Util.verbose(" -> type name: " + typeName); //$NON-NLS-1$
Util.verbose(" -> project: "+project.getElementName()); //$NON-NLS-1$
Util.verbose(" -> type: " + type.getElementName()); //$NON-NLS-1$
}
return type;
}
}
}
}
catch (JavaModelException jme) {
// give up
}
return null;
}
/**
* Find type in the given modules 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, IPackageFragmentRoot[] moduleContext) {
return findType(typeName,
packageName,
partialMatch,
acceptFlags,
true/* consider secondary types */,
false/* do NOT wait for indexes */,
checkRestrictions,
null,
moduleContext);
}
/**
* 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) {
return findType(typeName,
packageName,
partialMatch,
acceptFlags,
considerSecondaryTypes,
waitForIndexes,
checkRestrictions,
monitor,
null); // no module
}
/**
* 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,
IPackageFragmentRoot[] moduleContext) {
if (packageName == null || packageName.length() == 0) {
packageName= IPackageFragment.DEFAULT_PACKAGE_NAME;
} else if (typeName.length() > 0 && ScannerHelper.isLowerCase(typeName.charAt(0))) {
// see if this is a known package and not a type
if (findPackageFragments(packageName + "." + typeName, false) != null) return null; //$NON-NLS-1$
}
// Look for concerned package fragments
JavaElementRequestor elementRequestor = new JavaElementRequestor();
seekPackageFragments(packageName, false, elementRequestor, moduleContext);
IPackageFragment[] packages= elementRequestor.getPackageFragments();
// Try to find type in package fragments list
IType type = null;
int length= packages.length;
HashSet projects = null;
IJavaProject javaProject = null;
Answer suggestedAnswer = null;
for (int i= 0; i < length; i++) {
type = findType(typeName, packages[i], partialMatch, acceptFlags, waitForIndexes, considerSecondaryTypes);
if (type != null) {
AccessRestriction accessRestriction = null;
PackageFragmentRoot root = (PackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
ClasspathEntry entry = (ClasspathEntry) this.rootToResolvedEntries.get(root);
if (entry != null) { // reverse map always contains resolved CP entry
if (checkRestrictions) {
accessRestriction = getViolatedRestriction(typeName, packageName, entry, accessRestriction);
}
}
Answer answer = new Answer(type, accessRestriction, entry,
getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get));
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 (javaProject == null) {
javaProject = packages[i].getJavaProject();
} else if (projects == null) {
if (!javaProject.equals(packages[i].getJavaProject())) {
projects = new HashSet(3);
projects.add(javaProject);
projects.add(packages[i].getJavaProject());
}
} else {
projects.add(packages[i].getJavaProject());
}
}
}
if (suggestedAnswer != null)
// no better answer was found
return suggestedAnswer;
// If type was not found, try to find it as secondary in source folders
if (considerSecondaryTypes && javaProject != null) {
if (projects == null) {
type = findSecondaryType(packageName, typeName, javaProject, waitForIndexes, monitor);
} else {
Iterator allProjects = projects.iterator();
while (type == null && allProjects.hasNext()) {
type = findSecondaryType(packageName, typeName, (IJavaProject) allProjects.next(), waitForIndexes, monitor);
}
}
}
if (type != null) {
ICompilationUnit unit = type.getCompilationUnit();
if (unit != null && unit.isWorkingCopy()) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=421902
IType[] types = null;
try {
types = unit.getTypes();
} catch (JavaModelException e) {
return null;
}
boolean typeFound = false;
for (int i = 0, typesLength = types == null ? 0 : types.length; i < typesLength; i++) {
if (types[i].getElementName().equals(typeName)) {
typeFound = true;
break;
}
}
if (!typeFound) type = null;
}
}
return type == null ? null : new Answer(type, null, null);
}
public static IModule getModuleDescriptionInfo(IModuleDescription moduleDesc) {
if (moduleDesc != null) {
try {
if (moduleDesc instanceof AutoModule) {
boolean nameFromManifest = ((AutoModule) moduleDesc).isAutoNameFromManifest();
return IModule.createAutomatic(moduleDesc.getElementName().toCharArray(), nameFromManifest);
} else {
return ((AbstractModule) moduleDesc).getModuleInfo();
}
} catch (JavaModelException e) {
if (!e.isDoesNotExist())
Util.log(e);
}
}
return null;
}
/** Internal utility, which is able to answer explicit and automatic modules. */
static IModuleDescription getModuleDescription(JavaProject project, IPackageFragmentRoot root, Map<IPackageFragmentRoot,IModuleDescription> cache, Function<IPackageFragmentRoot,IClasspathEntry> rootToEntry) {
IModuleDescription module = cache.get(root);
if (module != null)
return module != NO_MODULE ? module : null;
if (!Objects.equals(project, root.getJavaProject())) {
IClasspathEntry classpathEntry2 = rootToEntry.apply(root);
if (classpathEntry2 instanceof ClasspathEntry) {
if (!((ClasspathEntry) classpathEntry2).isModular()) {
// not on the module path and not a local source folder
cache.put(root, NO_MODULE);
return null;
}
}
}
try {
if (root.getKind() == IPackageFragmentRoot.K_SOURCE)
module = root.getJavaProject().getModuleDescription(); // from any root in this project
} catch (JavaModelException e) {
cache.put(root, NO_MODULE);
return null;
}
if (module == null) {
// 2nd attempt: try automatic module:
IClasspathEntry classpathEntry = rootToEntry.apply(root);
if (classpathEntry instanceof ClasspathEntry) {
if (((ClasspathEntry) classpathEntry).isModular()) {
module = root.getModuleDescription();
if (module == null) {
// modular but no module-info implies this is an automatic module
module = ((PackageFragmentRoot) root).getAutomaticModuleDescription(classpathEntry);
}
}
} else if (root instanceof JrtPackageFragmentRoot) {
module = root.getModuleDescription(); // always considered modular
}
}
cache.put(root, module != null ? module : NO_MODULE);
return module;
}
public IModule getModuleDescriptionInfo(PackageFragmentRoot root) {
IModuleDescription desc = getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get);
if (desc != null) {
return getModuleDescriptionInfo(desc);
}
return null;
}
private AccessRestriction getViolatedRestriction(String typeName, String packageName, ClasspathEntry entry, AccessRestriction accessRestriction) {
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 Java project from which
* this name lookup was obtained.
*
* @param name the name of the type to find
* @param pkg the package to search
* @param partialMatch partial name matches qualify when <code>true</code>,
* only exact name matches qualify when <code>false</code>
* @param acceptFlags a bit mask describing if classes, interfaces or both classes and interfaces
* are desired results. If no flags are specified, all types are returned.
* @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, IPackageFragment pkg, boolean partialMatch, int acceptFlags, boolean waitForIndices, boolean considerSecondaryTypes) {
if (pkg == null)
return null;
SingleTypeRequestor typeRequestor = new SingleTypeRequestor();
seekTypes(name, pkg, partialMatch, acceptFlags, typeRequestor, considerSecondaryTypes);
IType type = typeRequestor.getType();
if (type == null && considerSecondaryTypes) {
type = findSecondaryType(pkg.getElementName(), name, pkg.getJavaProject(), waitForIndices, null);
}
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 Java 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, IPackageFragment pkg, boolean partialMatch, int acceptFlags) {
if (pkg == null) return null;
// Return first found (ignore duplicates).
SingleTypeRequestor typeRequestor = new SingleTypeRequestor();
seekTypes(name, pkg, partialMatch, acceptFlags, typeRequestor, false);
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 Java project from which this name lookup was obtained.
*
* @param name the name of the type to find
* @param partialMatch partial name matches qualify when <code>true</code>,
* only exact name matches qualify when <code>false</code>
* @param acceptFlags a bit mask describing if classes, interfaces or both classes and interfaces
* are desired results. If no flags are specified, all types are returned.
*
* @see #ACCEPT_CLASSES
* @see #ACCEPT_INTERFACES
* @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('.');
if (index == 0) {
return null; // bug 377710 - e.g. ".Foo" (no package, but not "default" package)
}
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, considerSecondaryTypes, waitForIndexes, checkRestrictions, monitor);
}
public Answer findModule(char[] moduleName) {
JavaElementRequestor requestor = new JavaElementRequestor();
seekModule(moduleName, false, requestor);
IModuleDescription[] modules = requestor.getModules();
if (modules.length == 0) {
try {
// FIXME(SHMOD): only considers source modules?? (MODULEPATH container is only experimental)
JavaModelManager.getModulePathManager().seekModule(moduleName, false, requestor);
modules = requestor.getModules();
} catch (JavaModelException e) {
// TODO Auto-generated catch block
}
}
if (modules.length > 0) { // TODO what to do??
return new Answer(modules[0]);
}
return null;
}
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.packageFragments.get(pkgName) != null;
}
public boolean isPackage(String[] pkgName, IPackageFragmentRoot[] moduleContext) {
if (moduleContext == null) // includes the case where looking for module UNNAMED or ANY
return isPackage(pkgName);
for (IPackageFragmentRoot moduleRoot : moduleContext) {
if (moduleRoot.getPackageFragment(String.join(".", pkgName)).exists()) //$NON-NLS-1$
return true;
}
return false;
}
private boolean moduleMatches(IPackageFragmentRoot root, IPackageFragmentRoot[] moduleContext) {
for (IPackageFragmentRoot moduleRoot : moduleContext)
if (moduleRoot.equals(root))
return true;
return false;
}
/**
* Returns true if the given element's name matches the
* specified <code>searchName</code>, otherwise false.
*
* <p>The <code>partialMatch</code> argument indicates partial matches
* should be considered.
* NOTE: in partialMatch mode, the case will be ignored, and the searchName must already have
* been lowercased.
*/
protected boolean nameMatches(String searchName, IJavaElement element, boolean partialMatch) {
if (partialMatch) {
// partial matches are used in completion mode, thus case insensitive mode
return element.getElementName().toLowerCase().startsWith(searchName);
} else {
return element.getElementName().equals(searchName);
}
}
/**
* 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, ICompilationUnit 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.equalsIgnoreJavaLikeExtension(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>IJavaProject</code>
* this <code>NameLookup</code> was obtained from.
*
* @param partialMatch partial name matches qualify when <code>true</code>;
* only exact name matches qualify when <code>false</code>
*/
public void seekPackageFragments(String name, boolean partialMatch, IJavaElementRequestor requestor, IPackageFragmentRoot[] moduleContext) {
if (moduleContext == null) {
seekPackageFragments(name, partialMatch, requestor);
return;
}
if (partialMatch) {
seekModuleAwarePartialPackageFragments(name, requestor, moduleContext);
return;
}
for (IPackageFragmentRoot moduleRoot : moduleContext) {
IPackageFragment fragment = moduleRoot.getPackageFragment(name);
if (fragment.exists())
requestor.acceptPackageFragment(fragment);
}
}
/**
* Notifies the given requestor of all package fragments with the
* given name. Checks the requestor at regular intervals to see if the
* requestor has canceled. The domain of
* the search is bounded by the <code>IJavaProject</code>
* this <code>NameLookup</code> was obtained from.
*
* @param partialMatch partial name matches qualify when <code>true</code>;
* only exact name matches qualify when <code>false</code>
*/
public void seekTypes(String pkgName, String name, boolean partialMatch, IJavaElementRequestor requestor,
int acceptFlags, IPackageFragmentRoot[] moduleContext, String moduleName) {
Selector selector = new Selector(moduleName);
seekPackageFragments(pkgName, true /*partialMatch*/, selector, moduleContext);
if (selector.pkgFragments.size() == 0) return;
for (IPackageFragment pkg : selector.pkgFragments) {
seekTypes(name, pkg, partialMatch, acceptFlags, requestor);
}
}
private void seekModuleAwarePartialPackageFragments(String name, IJavaElementRequestor requestor, IPackageFragmentRoot[] moduleContext) {
boolean allPrefixMatch = CharOperation.equals(name.toCharArray(), CharOperation.ALL_PREFIX);
String lName = name.toLowerCase();
Arrays.stream(this.packageFragments.keyTable)
.filter(k -> k != null)
.filter(k -> allPrefixMatch || Util.concatWith((String[])k, '.').toLowerCase().startsWith(lName))
.forEach(k -> {
checkModulePackages(requestor, moduleContext, this.packageFragments.getIndex(k));
});
}
private void checkModulePackages(IJavaElementRequestor requestor, IPackageFragmentRoot[] moduleContext, int pkgIndex) {
Object value = this.packageFragments.valueTable[pkgIndex];
// reuse existing String[]
String[] pkgName = (String[]) this.packageFragments.keyTable[pkgIndex];
if (value instanceof PackageFragmentRoot) {
PackageFragmentRoot root = (PackageFragmentRoot) value;
if (moduleMatches(root, moduleContext))
requestor.acceptPackageFragment(root.getPackageFragment(pkgName));
} else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) value;
if (roots != null) {
for (int i = 0, length = roots.length; i < length; i++) {
if (requestor.isCanceled())
return;
PackageFragmentRoot root = (PackageFragmentRoot) roots[i];
if (moduleMatches(root, moduleContext))
requestor.acceptPackageFragment(root.getPackageFragment(pkgName));
}
}
}
}
@FunctionalInterface
interface IPrefixMatcherCharArray { // note the reversal in the order of params wrt to the string version.
boolean matches(char[] prefix, char[] name, boolean isCaseSensitive);
}
/**
* Notifies the given requestor of all package fragments with the
* given name. Checks the requestor at regular intervals to see if the
* requestor has canceled. The domain of
* the search is bounded by the <code>IJavaProject</code>
* this <code>NameLookup</code> was obtained from.
*
* @param partialMatch partial name matches qualify when <code>true</code>;
* only exact name matches qualify when <code>false</code>
*/
public void seekPackageFragments(String name, boolean partialMatch, IJavaElementRequestor requestor) {
/* 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.packageFragments.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, partialMatch)) {
Object value = this.packageFragments.valueTable[i];
if (value instanceof PackageFragmentRoot) {
PackageFragmentRoot root = (PackageFragmentRoot) value;
requestor.acceptPackageFragment(root.getPackageFragment(pkgName));
} else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) value;
for (int j = 0, length2 = roots.length; j < length2; j++) {
if (requestor.isCanceled())
return;
PackageFragmentRoot root = (PackageFragmentRoot) roots[j];
requestor.acceptPackageFragment(root.getPackageFragment(pkgName));
}
}
}
}
} else {
String[] splittedName = Util.splitOn('.', name, 0, name.length());
int pkgIndex = this.packageFragments.getIndex(splittedName);
if (pkgIndex != -1) {
Object value = this.packageFragments.valueTable[pkgIndex];
// reuse existing String[]
String[] pkgName = (String[]) this.packageFragments.keyTable[pkgIndex];
if (value instanceof PackageFragmentRoot) {
requestor.acceptPackageFragment(((PackageFragmentRoot) value).getPackageFragment(pkgName));
} else {
IPackageFragmentRoot[] roots = (IPackageFragmentRoot[]) value;
if (roots != null) {
for (int i = 0, length = roots.length; i < length; i++) {
if (requestor.isCanceled())
return;
PackageFragmentRoot root = (PackageFragmentRoot) roots[i];
requestor.acceptPackageFragment(root.getPackageFragment(pkgName));
}
}
}
}
}
}
public void seekTypes(String name, IPackageFragment pkg, boolean partialMatch, int acceptFlags, IJavaElementRequestor requestor) {
seekTypes(name, pkg, partialMatch, acceptFlags, requestor, true);
}
public void seekModuleReferences(String name, IJavaElementRequestor requestor, IJavaProject javaProject) {
seekModule(name.toCharArray(), true /* prefix */, requestor);
}
public void seekModule(char[] name, boolean prefixMatch, IJavaElementRequestor requestor) {
IPrefixMatcherCharArray prefixMatcher = prefixMatch
? CharOperation.equals(name, CharOperation.ALL_PREFIX)
? (x, y, isCaseSensitive) -> true
: CharOperation::prefixEquals
: CharOperation::equals;
int count= this.packageFragmentRoots.length;
for (int i= 0; i < count; i++) {
if (requestor.isCanceled())
return;
IPackageFragmentRoot root= this.packageFragmentRoots[i];
IModuleDescription module = null;
if (root instanceof JrtPackageFragmentRoot) {
if (!prefixMatcher.matches(name, root.getElementName().toCharArray(), false)) {
continue;
}
}
module = getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get);
if (module != null && prefixMatcher.matches(name, module.getElementName().toCharArray(), false)) {
requestor.acceptModule(module);
}
}
}
/**
* 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, IPackageFragment pkg, boolean partialMatch, int acceptFlags, IJavaElementRequestor requestor, boolean considerSecondaryTypes) {
/* if (VERBOSE) {
Util.verbose(" SEEKING TYPES"); //$NON-NLS-1$
Util.verbose(" -> name: " + name); //$NON-NLS-1$
Util.verbose(" -> pkg: " + ((JavaElement) 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;
}
PackageFragmentRoot root= (PackageFragmentRoot) pkg.getParent();
try {
// look in working copies first
int firstDot = -1;
String topLevelTypeName = null;
int packageFlavor= root.internalKind();
if (this.typesInWorkingCopies != null || packageFlavor == IPackageFragmentRoot.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, considerSecondaryTypes))
return;
}
// look in model
switch (packageFlavor) {
case IPackageFragmentRoot.K_BINARY :
matchName= matchName.replace('.', '$');
seekTypesInBinaryPackage(matchName, pkg, partialMatch, acceptFlags, requestor);
break;
case IPackageFragmentRoot.K_SOURCE :
seekTypesInSourcePackage(matchName, pkg, firstDot, partialMatch, topLevelTypeName, acceptFlags, requestor);
if (matchName.indexOf('$') != -1) {
matchName= matchName.replace('$', '.');
firstDot = matchName.indexOf('.');
if (!partialMatch)
topLevelTypeName = firstDot == -1 ? matchName : matchName.substring(0, firstDot);
seekTypesInSourcePackage(matchName, pkg, firstDot, partialMatch, topLevelTypeName, 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) {
long start = -1;
if (VERBOSE)
start = System.currentTimeMillis();
try {
if (!partialMatch) {
// exact match
if (requestor.isCanceled()) return;
ClassFile classFile = new ClassFile((PackageFragment) pkg, name);
if (classFile.existsUsingJarTypeCache()) {
IType type = classFile.getType();
if (acceptType(type, acceptFlags, false/*not a source type*/)) {
requestor.acceptType(type);
}
}
} else {
IJavaElement[] classFiles= null;
try {
classFiles= pkg.getChildren();
} 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 = Util.localTypeName(name, index, name.length());
// unqualifiedName is empty if the name ends with a '$' sign.
// See http://dev.eclipse.org/bugs/show_bug.cgi?id=14642
}
int matchLength = name.length();
for (int i = 0; i < length; i++) {
if (requestor.isCanceled())
return;
IJavaElement classFile= classFiles[i];
// MatchName will never have the extension ".class" and the elementName always will.
String elementName = classFile.getElementName();
if (elementName.regionMatches(true /*ignore case*/, 0, name, 0, matchLength)) {
if (classFile instanceof IOrdinaryClassFile) {
IType type = ((IOrdinaryClassFile) classFile).getType();
String typeName = type.getElementName();
if (typeName.length() > 0 && !Character.isDigit(typeName.charAt(0))) { //not an anonymous type
if (nameMatches(unqualifiedName, type, true/*partial match*/) && acceptType(type, acceptFlags, false/*not a source type*/))
requestor.acceptType(type);
}
}
}
}
}
} finally {
if (VERBOSE)
this.timeSpentInSeekTypesInBinaryPackage += System.currentTimeMillis()-start;
}
}
/**
* Performs type search in a source package.
*/
protected void seekTypesInSourcePackage(
String name,
IPackageFragment pkg,
int firstDot,
boolean partialMatch,
String topLevelTypeName,
int acceptFlags,
IJavaElementRequestor requestor) {
long start = -1;
if (VERBOSE)
start = System.currentTimeMillis();
try {
if (!partialMatch) {
try {
IJavaElement[] compilationUnits = pkg.getChildren();
for (int i = 0, length = compilationUnits.length; i < length; i++) {
if (requestor.isCanceled())
return;
IJavaElement cu = compilationUnits[i];
String cuName = cu.getElementName();
int lastDot = cuName.lastIndexOf('.');
if (lastDot != topLevelTypeName.length() || !topLevelTypeName.regionMatches(0, cuName, 0, lastDot))
continue;
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=351697
// If we are looking at source location, just ignore binary types
if (!(cu instanceof ICompilationUnit))
continue;
IType type = ((ICompilationUnit) 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 (JavaModelException e) {
// package doesn't exist -> ignore
}
} else {
try {
String cuPrefix = firstDot == -1 ? name : name.substring(0, firstDot);
IJavaElement[] compilationUnits = pkg.getChildren();
for (int i = 0, length = compilationUnits.length; i < length; i++) {
if (requestor.isCanceled())
return;
IJavaElement cu = compilationUnits[i];
if (!cu.getElementName().toLowerCase().startsWith(cuPrefix))
continue;
try {
IType[] types = ((ICompilationUnit) cu).getTypes();
for (int j = 0, typeLength = types.length; j < typeLength; j++)
seekTypesInTopLevelType(name, firstDot, types[j], requestor, acceptFlags);
} catch (JavaModelException e) {
// cu doesn't exist -> ignore
}
}
} catch (JavaModelException e) {
// package doesn't exist -> ignore
}
}
} finally {
if (VERBOSE)
this.timeSpentInSeekTypesInSourcePackage += System.currentTimeMillis()-start;
}
}
private boolean isPrimaryType(String name, IType type, boolean partialMatch) {
/*
* Please have a look at: NameLookup#NameLookup
* The HashTable this.typesInWorkingCopies contains values which are HashTables themselves.
* The values of these HashTables are either of IType or IType[].
* These values are types belonging to a compilation unit. Please check:
* CompilationUnit#getTypes().
* Therefore the parents of these types would be compilation units.
*/
ICompilationUnit cu = (ICompilationUnit) type.getParent();
String cuName = cu.getElementName().substring(0, cu.getElementName().lastIndexOf('.'));
/*
* Secondary types along with primary types have their parent as the compilation unit.
* The names of the primary type would match with their compilation unit.
*/
if (!cuName.equals(type.getElementName()))
return false;
if (partialMatch) {
return cuName.regionMatches(0, name, 0, name.length());
} else {
return cuName.equals(name);
}
}
/**
* 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, IJavaElementRequestor requestor, int acceptFlags) {
IType[] types= null;
try {
types= type.getTypes();
} catch (JavaModelException 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, IJavaElementRequestor 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,
IPackageFragment pkg,
int firstDot,
boolean partialMatch,
String topLevelTypeName,
int acceptFlags,
IJavaElementRequestor requestor,
boolean considerSecondaryTypes) {
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 (!considerSecondaryTypes && !isPrimaryType(name, (IType) object, false))
return false;
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) {
// all types where deleted -> type is hidden, OR it is the fake type package-info
String packageInfoName = String.valueOf(TypeConstants.PACKAGE_INFO_NAME);
if (packageInfoName.equals(name))
requestor.acceptType(pkg.getCompilationUnit(packageInfoName.concat(SUFFIX_STRING_java)).getType(name));
return true;
}
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) {
if (!considerSecondaryTypes && !isPrimaryType(name, (IType) object, true))
continue;
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;
}
public boolean hasCompilationUnit(char[][] pkgName, IPackageFragmentRoot[] moduleContext) {
String packageName = CharOperation.toString(pkgName);
if (packageName == null || packageName.length() == 0) {
packageName= IPackageFragment.DEFAULT_PACKAGE_NAME;
}
// Look for concerned package fragments
JavaElementRequestor elementRequestor = new JavaElementRequestor();
seekPackageFragments(packageName, false, elementRequestor, moduleContext);
IPackageFragment[] packages= elementRequestor.getPackageFragments();
for (IPackageFragment fragment : packages) {
try {
if (fragment.containsJavaResources())
return true;
} catch (JavaModelException e) {
// silent
}
}
return false;
}
}