blob: 559585675962d962342f6a813d0ea78b924a99c0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 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.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.*;
import org.eclipse.jdt.internal.codeassist.ISearchRequestor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.IModule;
import org.eclipse.jdt.internal.compiler.env.IModule.IModuleReference;
import org.eclipse.jdt.internal.compiler.env.IModule.IPackageExport;
import org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment;
import org.eclipse.jdt.internal.compiler.env.ISourceType;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.env.IUpdatableModule.UpdateKind;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.ModuleBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.core.NameLookup.Answer;
import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
import org.eclipse.jdt.internal.core.search.IRestrictedAccessConstructorRequestor;
import org.eclipse.jdt.internal.core.search.IRestrictedAccessTypeRequestor;
import org.eclipse.jdt.internal.core.search.indexing.IndexManager;
import org.eclipse.jdt.internal.core.search.processing.IJob;
import org.eclipse.jdt.internal.core.util.Util;
/**
* This class provides a <code>SearchableBuilderEnvironment</code> for code assist which
* uses the Java model as a search tool.
*/
public class SearchableEnvironment
implements IModuleAwareNameEnvironment, IJavaSearchConstants {
public NameLookup nameLookup;
protected ICompilationUnit unitToSkip;
protected org.eclipse.jdt.core.ICompilationUnit[] workingCopies;
protected WorkingCopyOwner owner;
protected JavaProject project;
protected IJavaSearchScope searchScope;
protected boolean checkAccessRestrictions;
// moduleName -> IPackageFragmentRoot[](lazily populated)
private Map<String,IPackageFragmentRoot[]> knownModuleLocations; // null indicates: not using JPMS
private boolean excludeTestCode;
private ModuleUpdater moduleUpdater;
private Map<IPackageFragmentRoot,IModuleDescription> rootToModule;
@Deprecated
public SearchableEnvironment(JavaProject project, org.eclipse.jdt.core.ICompilationUnit[] workingCopies) throws JavaModelException {
this(project, workingCopies, false);
}
/**
* Creates a SearchableEnvironment on the given project
*/
public SearchableEnvironment(JavaProject project, org.eclipse.jdt.core.ICompilationUnit[] workingCopies, boolean excludeTestCode) throws JavaModelException {
this.project = project;
this.excludeTestCode = excludeTestCode;
this.checkAccessRestrictions =
!JavaCore.IGNORE.equals(project.getOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, true))
|| !JavaCore.IGNORE.equals(project.getOption(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE, true));
this.workingCopies = workingCopies;
this.nameLookup = project.newNameLookup(workingCopies, excludeTestCode);
if (CompilerOptions.versionToJdkLevel(project.getOption(JavaCore.COMPILER_COMPLIANCE, true)) >= ClassFileConstants.JDK9) {
this.knownModuleLocations = new HashMap<>();
}
if (CompilerOptions.versionToJdkLevel(project.getOption(JavaCore.COMPILER_COMPLIANCE, true)) >= ClassFileConstants.JDK9) {
this.moduleUpdater = new ModuleUpdater(project);
if (!excludeTestCode) {
IClasspathEntry[] expandedClasspath = project.getExpandedClasspath();
if(Arrays.stream(expandedClasspath).anyMatch(e -> e.isTest())) {
this.moduleUpdater.addReadUnnamedForNonEmptyClasspath(project, expandedClasspath);
}
}
for (IClasspathEntry entry : project.getRawClasspath())
if(!excludeTestCode || !entry.isTest())
this.moduleUpdater.computeModuleUpdates(entry);
}
}
/**
* Note: this is required for (abandoned) Scala-IDE
*/
@Deprecated
public SearchableEnvironment(JavaProject project, WorkingCopyOwner owner) throws JavaModelException {
this(project, owner, false);
}
/**
* Creates a SearchableEnvironment on the given project
*/
public SearchableEnvironment(JavaProject project, WorkingCopyOwner owner, boolean excludeTestCode) throws JavaModelException {
this(project, owner == null ? null : JavaModelManager.getJavaModelManager().getWorkingCopies(owner, true/*add primary WCs*/), excludeTestCode);
this.owner = owner;
}
private static int convertSearchFilterToModelFilter(int searchFilter) {
switch (searchFilter) {
case IJavaSearchConstants.CLASS:
return NameLookup.ACCEPT_CLASSES;
case IJavaSearchConstants.INTERFACE:
return NameLookup.ACCEPT_INTERFACES;
case IJavaSearchConstants.ENUM:
return NameLookup.ACCEPT_ENUMS;
case IJavaSearchConstants.ANNOTATION_TYPE:
return NameLookup.ACCEPT_ANNOTATIONS;
case IJavaSearchConstants.CLASS_AND_ENUM:
return NameLookup.ACCEPT_CLASSES | NameLookup.ACCEPT_ENUMS;
case IJavaSearchConstants.CLASS_AND_INTERFACE:
return NameLookup.ACCEPT_CLASSES | NameLookup.ACCEPT_INTERFACES;
default:
return NameLookup.ACCEPT_ALL;
}
}
/**
* Returns the given type in the the given package if it exists,
* otherwise <code>null</code>.
*/
protected NameEnvironmentAnswer find(String typeName, String packageName, IPackageFragmentRoot[] moduleContext) {
if (packageName == null)
packageName = IPackageFragment.DEFAULT_PACKAGE_NAME;
if (this.owner != null) {
String source = this.owner.findSource(typeName, packageName);
if (source != null) {
IJavaElement moduleElement = (moduleContext != null && moduleContext.length > 0) ? moduleContext[0] : null;
ICompilationUnit cu = new BasicCompilationUnit(
source.toCharArray(),
CharOperation.splitOn('.', packageName.toCharArray()),
typeName + Util.defaultJavaExtension(),
moduleElement);
return new NameEnvironmentAnswer(cu, null);
}
}
NameLookup.Answer answer =
this.nameLookup.findType(
typeName,
packageName,
false/*exact match*/,
NameLookup.ACCEPT_ALL,
this.checkAccessRestrictions,
moduleContext);
if (answer != null) {
// construct name env answer
if (answer.type instanceof BinaryType) { // BinaryType
try {
char[] moduleName = answer.module != null ? answer.module.getElementName().toCharArray() : null;
return new NameEnvironmentAnswer((IBinaryType) ((BinaryType) answer.type).getElementInfo(), answer.restriction, moduleName);
} catch (JavaModelException npe) {
// fall back to using owner
}
} else { //SourceType
try {
// retrieve the requested type
SourceTypeElementInfo sourceType = (SourceTypeElementInfo)((SourceType) answer.type).getElementInfo();
ISourceType topLevelType = sourceType;
while (topLevelType.getEnclosingType() != null) {
topLevelType = topLevelType.getEnclosingType();
}
// find all siblings (other types declared in same unit, since may be used for name resolution)
IType[] types = sourceType.getHandle().getCompilationUnit().getTypes();
ISourceType[] sourceTypes = new ISourceType[types.length];
// in the resulting collection, ensure the requested type is the first one
sourceTypes[0] = sourceType;
int length = types.length;
for (int i = 0, index = 1; i < length; i++) {
ISourceType otherType =
(ISourceType) ((JavaElement) types[i]).getElementInfo();
if (!otherType.equals(topLevelType) && index < length) // check that the index is in bounds (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=62861)
sourceTypes[index++] = otherType;
}
char[] moduleName = answer.module != null ? answer.module.getElementName().toCharArray() : null;
return new NameEnvironmentAnswer(sourceTypes, answer.restriction, getExternalAnnotationPath(answer.entry), moduleName);
} catch (JavaModelException jme) {
if (jme.isDoesNotExist() && String.valueOf(TypeConstants.PACKAGE_INFO_NAME).equals(typeName)) {
// in case of package-info.java the type doesn't exist in the model,
// but the CU may still help in order to fetch package level annotations.
return new NameEnvironmentAnswer((ICompilationUnit)answer.type.getParent(), answer.restriction);
}
// no usable answer
}
}
}
return null;
}
private String getExternalAnnotationPath(IClasspathEntry entry) {
if (entry == null)
return null;
IPath path = ClasspathEntry.getExternalAnnotationPath(entry, this.project.getProject(), true);
if (path == null)
return null;
return path.toOSString();
}
/**
* Find the modules that start with the given prefix.
* A valid prefix is a qualified name separated by periods
* (ex. java.util).
* The packages found are passed to:
* ISearchRequestor.acceptModule(char[][] moduleName)
*/
public void findModules(char[] prefix, ISearchRequestor requestor, IJavaProject javaProject) {
this.nameLookup.seekModule(prefix, true, new SearchableEnvironmentRequestor(requestor));
}
/**
* Find the packages that start with the given prefix.
* A valid prefix is a qualified name separated by periods
* (ex. java.util).
* The packages found are passed to:
* ISearchRequestor.acceptPackage(char[][] packageName)
*/
public void findPackages(char[] prefix, ISearchRequestor requestor) {
this.nameLookup.seekPackageFragments(
new String(prefix),
true,
new SearchableEnvironmentRequestor(requestor));
}
/**
* Find the packages that start with the given prefix and belong to the given module.
* A valid prefix is a qualified name separated by periods
* (ex. java.util).
* The packages found are passed to:
* ISearchRequestor.acceptPackage(char[][] packageName)
*/
public void findPackages(char[] prefix, ISearchRequestor requestor, IPackageFragmentRoot[] moduleContext, boolean followRequires) {
this.nameLookup.seekPackageFragments(
new String(prefix),
true,
new SearchableEnvironmentRequestor(requestor), moduleContext);
if (followRequires && this.knownModuleLocations != null) {
try {
boolean isMatchAllPrefix = CharOperation.equals(CharOperation.ALL_PREFIX, prefix);
Set<IModuleDescription> modDescs = new HashSet<>();
for (IPackageFragmentRoot root : moduleContext) {
IModuleDescription desc = root.getJavaProject().getModuleDescription();
if (desc instanceof AbstractModule)
modDescs.add(desc);
}
for (IModuleDescription md : modDescs) {
IModuleReference[] reqModules = ((AbstractModule) md).getRequiredModules();
char[] modName = md.getElementName().toCharArray();
for (IModuleReference moduleReference : reqModules) {
findPackagesFromRequires(prefix, isMatchAllPrefix, requestor, moduleReference, modName);
}
}
} catch (JavaModelException e) {
// silent
}
}
}
private void findPackagesFromRequires(char[] prefix, boolean isMatchAllPrefix, ISearchRequestor requestor, IModuleReference moduleReference, char[] clientModuleName) {
IPackageFragmentRoot[] fragmentRoots = findModuleContext(moduleReference.name());
if (fragmentRoots == null) return;
for (IPackageFragmentRoot root : fragmentRoots) {
IJavaProject requiredProject = root.getJavaProject();
try {
IModuleDescription module = requiredProject.getModuleDescription();
if (module instanceof AbstractModule) {
AbstractModule requiredModule = (AbstractModule) module;
for (IPackageExport packageExport : requiredModule.getExportedPackages()) {
if (!packageExport.isQualified() || CharOperation.containsEqual(packageExport.targets(), clientModuleName)) {
char[] exportName = packageExport.name();
if (isMatchAllPrefix || CharOperation.prefixEquals(prefix, exportName))
requestor.acceptPackage(exportName);
}
}
for (IModuleReference moduleRef2 : requiredModule.getRequiredModules()) {
if (moduleRef2.isTransitive())
findPackagesFromRequires(prefix, isMatchAllPrefix, requestor, moduleRef2, clientModuleName);
}
}
} catch (JavaModelException e) {
// silent
}
}
}
/**
* Find the top-level types that are defined
* in the current environment and whose simple name matches the given name.
*
* The types found are passed to one of the following methods (if additional
* information is known about the types):
* ISearchRequestor.acceptType(char[][] packageName, char[] typeName)
* ISearchRequestor.acceptClass(char[][] packageName, char[] typeName, int modifiers)
* ISearchRequestor.acceptInterface(char[][] packageName, char[] typeName, int modifiers)
*
* This method can not be used to find member types... member
* types are found relative to their enclosing type.
*/
public void findExactTypes(char[] name, final boolean findMembers, int searchFor, final ISearchRequestor storage) {
try {
final String excludePath;
if (this.unitToSkip != null) {
if (!(this.unitToSkip instanceof IJavaElement)) {
// revert to model investigation
findExactTypes(
new String(name),
storage,
convertSearchFilterToModelFilter(searchFor));
return;
}
excludePath = ((IJavaElement) this.unitToSkip).getPath().toString();
} else {
excludePath = null;
}
IProgressMonitor progressMonitor = new IProgressMonitor() {
boolean isCanceled = false;
@Override
public void beginTask(String n, int totalWork) {
// implements interface method
}
@Override
public void done() {
// implements interface method
}
@Override
public void internalWorked(double work) {
// implements interface method
}
@Override
public boolean isCanceled() {
return this.isCanceled;
}
@Override
public void setCanceled(boolean value) {
this.isCanceled = value;
}
@Override
public void setTaskName(String n) {
// implements interface method
}
@Override
public void subTask(String n) {
// implements interface method
}
@Override
public void worked(int work) {
// implements interface method
}
};
IRestrictedAccessTypeRequestor typeRequestor = new IRestrictedAccessTypeRequestor() {
@Override
public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path, AccessRestriction access) {
if (excludePath != null && excludePath.equals(path))
return;
if (!findMembers && enclosingTypeNames != null && enclosingTypeNames.length > 0)
return; // accept only top level types
storage.acceptType(packageName, simpleTypeName, enclosingTypeNames, modifiers, access);
}
};
try {
new BasicSearchEngine(this.workingCopies).searchAllTypeNames(
null,
SearchPattern.R_EXACT_MATCH,
name,
SearchPattern.R_EXACT_MATCH,
searchFor,
getSearchScope(),
typeRequestor,
CANCEL_IF_NOT_READY_TO_SEARCH,
progressMonitor);
} catch (OperationCanceledException e) {
findExactTypes(
new String(name),
storage,
convertSearchFilterToModelFilter(searchFor));
}
} catch (JavaModelException e) {
findExactTypes(
new String(name),
storage,
convertSearchFilterToModelFilter(searchFor));
}
}
/**
* Returns all types whose simple name matches with the given <code>name</code>.
*/
private void findExactTypes(String name, ISearchRequestor storage, int type) {
SearchableEnvironmentRequestor requestor =
new SearchableEnvironmentRequestor(storage, this.unitToSkip, this.project, this.nameLookup);
this.nameLookup.seekTypes(name, null, false, type, requestor);
}
/**
* Find a type in the given module or any module read by it.
* Does not check accessibility / unique visibility, but returns the first observable type found.
* @param compoundTypeName name of the sought type
* @param module start into the module graph
* @return the answer :)
*/
public NameEnvironmentAnswer findTypeInModules(char[][] compoundTypeName, ModuleBinding module) {
char[] nameForLookup = module.nameForLookup();
NameEnvironmentAnswer answer = findType(compoundTypeName, nameForLookup);
if (answer != null)
return answer;
if (LookupStrategy.get(nameForLookup) == LookupStrategy.Named) {
for (ModuleBinding required : module.getAllRequiredModules()) {
answer = findType(compoundTypeName, required.nameForLookup());
if (answer != null)
return answer;
}
}
return null;
}
/**
* @see org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment#findType(char[][],char[])
*/
@Override
public NameEnvironmentAnswer findType(char[][] compoundTypeName, char[] moduleName) {
if (compoundTypeName == null) return null;
boolean isNamedStrategy = LookupStrategy.get(moduleName) == LookupStrategy.Named;
IPackageFragmentRoot[] moduleLocations = isNamedStrategy ? findModuleContext(moduleName) : null;
int length = compoundTypeName.length;
if (length <= 1) {
if (length == 0) return null;
return find(new String(compoundTypeName[0]), null, moduleLocations);
}
int lengthM1 = length - 1;
char[][] packageName = new char[lengthM1][];
System.arraycopy(compoundTypeName, 0, packageName, 0, lengthM1);
return find(
new String(compoundTypeName[lengthM1]),
CharOperation.toString(packageName),
moduleLocations);
}
/**
* @see org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment#findType(char[],char[][],char[])
*/
@Override
public NameEnvironmentAnswer findType(char[] name, char[][] packageName, char[] moduleName) {
if (name == null) return null;
boolean isNamedStrategy = LookupStrategy.get(moduleName) == LookupStrategy.Named;
IPackageFragmentRoot[] moduleLocations = isNamedStrategy ? findModuleContext(moduleName) : null;
return find(
new String(name),
packageName == null || packageName.length == 0 ? null : CharOperation.toString(packageName),
moduleLocations);
}
/**
* Find the top-level types that are defined
* in the current environment and whose name starts with the
* given prefix. The prefix is a qualified name separated by periods
* or a simple name (ex. java.util.V or V).
*
* The types found are passed to one of the following methods (if additional
* information is known about the types):
* ISearchRequestor.acceptType(char[][] packageName, char[] typeName)
* ISearchRequestor.acceptClass(char[][] packageName, char[] typeName, int modifiers)
* ISearchRequestor.acceptInterface(char[][] packageName, char[] typeName, int modifiers)
*
* This method can not be used to find member types... member
* types are found relative to their enclosing type.
*/
public void findTypes(char[] prefix, final boolean findMembers, boolean camelCaseMatch, int searchFor, final ISearchRequestor storage) {
findTypes(prefix, findMembers, camelCaseMatch, searchFor, storage, null);
}
/**
* Must be used only by CompletionEngine.
* The progress monitor is used to be able to cancel completion operations
*
* Find the top-level types that are defined
* in the current environment and whose name starts with the
* given prefix. The prefix is a qualified name separated by periods
* or a simple name (ex. java.util.V or V).
*
* The types found are passed to one of the following methods (if additional
* information is known about the types):
* ISearchRequestor.acceptType(char[][] packageName, char[] typeName)
* ISearchRequestor.acceptClass(char[][] packageName, char[] typeName, int modifiers)
* ISearchRequestor.acceptInterface(char[][] packageName, char[] typeName, int modifiers)
*
* This method can not be used to find member types... member
* types are found relative to their enclosing type.
*/
public void findTypes(char[] prefix, final boolean findMembers, boolean camelCaseMatch, int searchFor, final ISearchRequestor storage, IProgressMonitor monitor) {
/*
if (true){
findTypes(new String(prefix), storage, NameLookup.ACCEPT_CLASSES | NameLookup.ACCEPT_INTERFACES);
return;
}
*/
try {
final String excludePath;
if (this.unitToSkip != null) {
if (!(this.unitToSkip instanceof IJavaElement)) {
// revert to model investigation
findTypes(
new String(prefix),
storage,
convertSearchFilterToModelFilter(searchFor));
return;
}
excludePath = ((IJavaElement) this.unitToSkip).getPath().toString();
} else {
excludePath = null;
}
int lastDotIndex = CharOperation.lastIndexOf('.', prefix);
char[] qualification, simpleName;
if (lastDotIndex < 0) {
qualification = null;
if (camelCaseMatch) {
simpleName = prefix;
} else {
simpleName = CharOperation.toLowerCase(prefix);
}
} else {
qualification = CharOperation.subarray(prefix, 0, lastDotIndex);
if (camelCaseMatch) {
simpleName = CharOperation.subarray(prefix, lastDotIndex + 1, prefix.length);
} else {
simpleName =
CharOperation.toLowerCase(
CharOperation.subarray(prefix, lastDotIndex + 1, prefix.length));
}
}
IProgressMonitor progressMonitor = new IProgressMonitor() {
boolean isCanceled = false;
@Override
public void beginTask(String name, int totalWork) {
// implements interface method
}
@Override
public void done() {
// implements interface method
}
@Override
public void internalWorked(double work) {
// implements interface method
}
@Override
public boolean isCanceled() {
return this.isCanceled;
}
@Override
public void setCanceled(boolean value) {
this.isCanceled = value;
}
@Override
public void setTaskName(String name) {
// implements interface method
}
@Override
public void subTask(String name) {
// implements interface method
}
@Override
public void worked(int work) {
// implements interface method
}
};
IRestrictedAccessTypeRequestor typeRequestor = new IRestrictedAccessTypeRequestor() {
@Override
public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path, AccessRestriction access) {
if (excludePath != null && excludePath.equals(path))
return;
if (!findMembers && enclosingTypeNames != null && enclosingTypeNames.length > 0)
return; // accept only top level types
storage.acceptType(packageName, simpleTypeName, enclosingTypeNames, modifiers, access);
}
};
int matchRule = SearchPattern.R_PREFIX_MATCH;
if (camelCaseMatch) matchRule |= SearchPattern.R_CAMELCASE_MATCH;
if (monitor != null) {
IndexManager indexManager = JavaModelManager.getIndexManager();
if (indexManager.awaitingJobsCount() == 0) {
// indexes were already there, so perform an immediate search to avoid any index rebuilt
new BasicSearchEngine(this.workingCopies).searchAllTypeNames(
qualification,
SearchPattern.R_EXACT_MATCH,
simpleName,
matchRule, // not case sensitive
searchFor,
getSearchScope(),
typeRequestor,
FORCE_IMMEDIATE_SEARCH,
progressMonitor);
} else {
// indexes were not ready, give the indexing a chance to finish small jobs by sleeping 100ms...
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Do nothing
}
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
if (indexManager.awaitingJobsCount() == 0) {
// indexes are now ready, so perform an immediate search to avoid any index rebuilt
new BasicSearchEngine(this.workingCopies).searchAllTypeNames(
qualification,
SearchPattern.R_EXACT_MATCH,
simpleName,
matchRule, // not case sensitive
searchFor,
getSearchScope(),
typeRequestor,
FORCE_IMMEDIATE_SEARCH,
progressMonitor);
} else {
// Indexes are still not ready, so look for types in the model instead of a search request
findTypes(
new String(prefix),
storage,
convertSearchFilterToModelFilter(searchFor));
}
}
} else {
try {
new BasicSearchEngine(this.workingCopies).searchAllTypeNames(
qualification,
SearchPattern.R_EXACT_MATCH,
simpleName,
matchRule, // not case sensitive
searchFor,
getSearchScope(),
typeRequestor,
CANCEL_IF_NOT_READY_TO_SEARCH,
progressMonitor);
} catch (OperationCanceledException e) {
findTypes(
new String(prefix),
storage,
convertSearchFilterToModelFilter(searchFor));
}
}
} catch (JavaModelException e) {
findTypes(
new String(prefix),
storage,
convertSearchFilterToModelFilter(searchFor));
}
}
/**
* Must be used only by CompletionEngine.
* The progress monitor is used to be able to cancel completion operations
*
* Find constructor declarations that are defined
* in the current environment and whose name starts with the
* given prefix. The prefix is a qualified name separated by periods
* or a simple name (ex. java.util.V or V).
*
* The constructors found are passed to one of the following methods:
* ISearchRequestor.acceptConstructor(...)
*/
public void findConstructorDeclarations(char[] prefix, boolean camelCaseMatch, final ISearchRequestor storage, IProgressMonitor monitor) {
try {
final String excludePath;
if (this.unitToSkip != null && this.unitToSkip instanceof IJavaElement) {
excludePath = ((IJavaElement) this.unitToSkip).getPath().toString();
} else {
excludePath = null;
}
int lastDotIndex = CharOperation.lastIndexOf('.', prefix);
char[] qualification, simpleName;
if (lastDotIndex < 0) {
qualification = null;
if (camelCaseMatch) {
simpleName = prefix;
} else {
simpleName = CharOperation.toLowerCase(prefix);
}
} else {
qualification = CharOperation.subarray(prefix, 0, lastDotIndex);
if (camelCaseMatch) {
simpleName = CharOperation.subarray(prefix, lastDotIndex + 1, prefix.length);
} else {
simpleName =
CharOperation.toLowerCase(
CharOperation.subarray(prefix, lastDotIndex + 1, prefix.length));
}
}
IProgressMonitor progressMonitor = new IProgressMonitor() {
boolean isCanceled = false;
@Override
public void beginTask(String name, int totalWork) {
// implements interface method
}
@Override
public void done() {
// implements interface method
}
@Override
public void internalWorked(double work) {
// implements interface method
}
@Override
public boolean isCanceled() {
return this.isCanceled;
}
@Override
public void setCanceled(boolean value) {
this.isCanceled = value;
}
@Override
public void setTaskName(String name) {
// implements interface method
}
@Override
public void subTask(String name) {
// implements interface method
}
@Override
public void worked(int work) {
// implements interface method
}
};
IRestrictedAccessConstructorRequestor constructorRequestor = new IRestrictedAccessConstructorRequestor() {
@Override
public void acceptConstructor(
int modifiers,
char[] simpleTypeName,
int parameterCount,
char[] signature,
char[][] parameterTypes,
char[][] parameterNames,
int typeModifiers,
char[] packageName,
int extraFlags,
String path,
AccessRestriction access) {
if (excludePath != null && excludePath.equals(path))
return;
storage.acceptConstructor(
modifiers,
simpleTypeName,
parameterCount,
signature,
parameterTypes,
parameterNames,
typeModifiers,
packageName,
extraFlags,
path,
access);
}
};
int matchRule = SearchPattern.R_PREFIX_MATCH;
if (camelCaseMatch) matchRule |= SearchPattern.R_CAMELCASE_MATCH;
if (monitor != null) {
IndexManager indexManager = JavaModelManager.getIndexManager();
// Wait for the end of indexing or a cancel
indexManager.performConcurrentJob(new IJob() {
@Override
public boolean belongsTo(String jobFamily) {
return true;
}
@Override
public void cancel() {
// job is cancelled through progress
}
@Override
public void ensureReadyToRun() {
// always ready
}
@Override
public boolean execute(IProgressMonitor progress) {
return progress == null || !progress.isCanceled();
}
@Override
public String getJobFamily() {
return ""; //$NON-NLS-1$
}
}, IJob.WaitUntilReady, monitor);
new BasicSearchEngine(this.workingCopies).searchAllConstructorDeclarations(
qualification,
simpleName,
matchRule,
getSearchScope(),
constructorRequestor,
FORCE_IMMEDIATE_SEARCH,
progressMonitor);
} else {
try {
new BasicSearchEngine(this.workingCopies).searchAllConstructorDeclarations(
qualification,
simpleName,
matchRule,
getSearchScope(),
constructorRequestor,
CANCEL_IF_NOT_READY_TO_SEARCH,
progressMonitor);
} catch (OperationCanceledException e) {
// Do nothing
}
}
} catch (JavaModelException e) {
// Do nothing
}
}
/**
* Returns all types whose name starts with the given (qualified) <code>prefix</code>.
*
* If the <code>prefix</code> is unqualified, all types whose simple name matches
* the <code>prefix</code> are returned.
*/
private void findTypes(String prefix, ISearchRequestor storage, int type) {
//TODO (david) should add camel case support
SearchableEnvironmentRequestor requestor =
new SearchableEnvironmentRequestor(storage, this.unitToSkip, this.project, this.nameLookup);
int index = prefix.lastIndexOf('.');
if (index == -1) {
this.nameLookup.seekTypes(prefix, null, true, type, requestor);
} else {
String packageName = prefix.substring(0, index);
JavaElementRequestor elementRequestor = new JavaElementRequestor();
this.nameLookup.seekPackageFragments(packageName, false, elementRequestor);
IPackageFragment[] fragments = elementRequestor.getPackageFragments();
if (fragments != null) {
String className = prefix.substring(index + 1);
for (int i = 0, length = fragments.length; i < length; i++)
if (fragments[i] != null)
this.nameLookup.seekTypes(className, fragments[i], true, type, requestor);
}
}
}
private IJavaSearchScope getSearchScope() {
if (this.searchScope == null) {
// Create search scope with visible entry on the project's classpath
if(this.checkAccessRestrictions) {
this.searchScope = BasicSearchEngine.createJavaSearchScope(this.excludeTestCode, new IJavaElement[] {this.project});
} else {
this.searchScope = BasicSearchEngine.createJavaSearchScope(this.excludeTestCode, this.nameLookup.packageFragmentRoots);
}
}
return this.searchScope;
}
/**
* @see org.eclipse.jdt.internal.compiler.env.IModuleAwareNameEnvironment#getModulesDeclaringPackage(char[][], char[], char[])
*/
@Override
public char[][] getModulesDeclaringPackage(char[][] parentPackageName, char[] name, char[] moduleName) {
String[] pkgName;
if (parentPackageName == null)
pkgName = new String[] {new String(name)};
else {
int length = parentPackageName.length;
pkgName = new String[length+1];
for (int i = 0; i < length; i++)
pkgName[i] = new String(parentPackageName[i]);
pkgName[length] = new String(name);
}
LookupStrategy strategy = LookupStrategy.get(moduleName);
switch (strategy) {
case Named:
if (this.knownModuleLocations != null) {
IPackageFragmentRoot[] moduleContext = findModuleContext(moduleName);
if (moduleContext != null) {
// (this.owner != null && this.owner.isPackage(pkgName)) // TODO(SHMOD) see old isPackage
if (this.nameLookup.isPackage(pkgName, moduleContext)) {
return new char[][] { moduleName };
}
}
}
return null;
case Unnamed:
case Any:
// if in pre-9 mode we may still search the unnamed module
if (this.knownModuleLocations == null) {
if ((this.owner != null && this.owner.isPackage(pkgName))
|| this.nameLookup.isPackage(pkgName))
return new char[][] { ModuleBinding.UNNAMED };
return null;
}
//$FALL-THROUGH$
case AnyNamed:
char[][] names = CharOperation.NO_CHAR_CHAR;
IPackageFragmentRoot[] packageRoots = this.nameLookup.packageFragmentRoots;
boolean containsUnnamed = false;
for (IPackageFragmentRoot packageRoot : packageRoots) {
IPackageFragmentRoot[] singleton = { packageRoot };
if (strategy.matches(singleton, locs -> locs[0] instanceof JrtPackageFragmentRoot || getModuleDescription(locs) != null)) {
if (this.nameLookup.isPackage(pkgName, singleton)) {
IModuleDescription moduleDescription = getModuleDescription(singleton);
char[] aName;
if (moduleDescription != null) {
aName = moduleDescription.getElementName().toCharArray();
} else {
if (containsUnnamed)
continue;
containsUnnamed = true;
aName = ModuleBinding.UNNAMED;
}
names = CharOperation.arrayConcat(names, aName);
}
}
}
return names == CharOperation.NO_CHAR_CHAR ? null : names;
default:
throw new IllegalArgumentException("Unexpected LookupStrategy "+strategy); //$NON-NLS-1$
}
}
@Override
public boolean hasCompilationUnit(char[][] pkgName, char[] moduleName, boolean checkCUs) {
LookupStrategy strategy = LookupStrategy.get(moduleName);
switch (strategy) {
case Named:
if (this.knownModuleLocations != null) {
IPackageFragmentRoot[] moduleContext = findModuleContext(moduleName);
if (moduleContext != null) {
// (this.owner != null && this.owner.isPackage(pkgName)) // TODO(SHMOD) see old isPackage
if (this.nameLookup.hasCompilationUnit(pkgName, moduleContext))
return true;
}
}
return false;
case Unnamed:
case Any:
// if in pre-9 mode we may still search the unnamed module
if (this.knownModuleLocations == null) {
if (this.nameLookup.hasCompilationUnit(pkgName, null))
return true;
}
//$FALL-THROUGH$
case AnyNamed:
IPackageFragmentRoot[] packageRoots = this.nameLookup.packageFragmentRoots;
for (IPackageFragmentRoot packageRoot : packageRoots) {
IPackageFragmentRoot[] singleton = { packageRoot };
if (strategy.matches(singleton, locs -> locs[0] instanceof JrtPackageFragmentRoot || getModuleDescription(locs) != null)) {
if (this.nameLookup.hasCompilationUnit(pkgName, singleton))
return true;
}
}
return false;
default:
throw new IllegalArgumentException("Unexpected LookupStrategy "+strategy); //$NON-NLS-1$
}
}
private IModuleDescription getModuleDescription(IPackageFragmentRoot[] roots) {
if (this.rootToModule == null) {
this.rootToModule = new HashMap<>();
}
for (IPackageFragmentRoot root : roots) {
IModuleDescription moduleDescription = NameLookup.getModuleDescription(this.project, root, this.rootToModule, this.nameLookup.rootToResolvedEntries::get);
if (moduleDescription != null)
return moduleDescription;
}
return null;
}
private IPackageFragmentRoot[] findModuleContext(char[] moduleName) {
IPackageFragmentRoot[] moduleContext = null;
if (this.knownModuleLocations != null && moduleName != null && moduleName.length > 0) {
moduleContext = this.knownModuleLocations.get(String.valueOf(moduleName));
if (moduleContext == null) {
Answer moduleAnswer = this.nameLookup.findModule(moduleName);
if (moduleAnswer != null) {
IProject currentProject = moduleAnswer.module.getJavaProject().getProject();
IJavaElement current = moduleAnswer.module.getParent();
while (moduleContext == null && current != null) {
switch (current.getElementType()) {
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
if (!((IPackageFragmentRoot) current).isExternal() && !(current instanceof JarPackageFragmentRoot)) {
current = current.getJavaProject();
} else {
moduleContext = new IPackageFragmentRoot[] { (IPackageFragmentRoot) current }; // TODO: validate
break;
}
//$FALL-THROUGH$
case IJavaElement.JAVA_PROJECT:
try {
moduleContext = getOwnedPackageFragmentRoots((IJavaProject) current);
} catch (JavaModelException e) {
// silent?
}
break;
default:
current = current.getParent();
if (current != null) {
try {
// detect when an element refers to a resource owned by another project:
IResource resource = current.getUnderlyingResource();
if (resource != null) {
IProject otherProject = resource.getProject();
if (otherProject != null && !otherProject.equals(currentProject)) {
IJavaProject otherJavaProject = JavaCore.create(otherProject);
if (otherJavaProject.exists())
moduleContext = getRootsForOutputLocation(otherJavaProject, resource);
}
}
} catch (JavaModelException e) {
Util.log(e, "Failed to find package fragment root for " + current); //$NON-NLS-1$
}
}
}
}
this.knownModuleLocations.put(String.valueOf(moduleName), moduleContext);
}
}
}
return moduleContext;
}
/**
* Returns a printable string for the array.
*/
protected String toStringChar(char[] name) {
return "[" //$NON-NLS-1$
+ new String(name) + "]" ; //$NON-NLS-1$
}
/**
* Returns a printable string for the array.
*/
protected String toStringCharChar(char[][] names) {
StringBuffer result = new StringBuffer();
for (int i = 0; i < names.length; i++) {
result.append(toStringChar(names[i]));
}
return result.toString();
}
@Override
public void cleanup() {
// nothing to do
}
@Override
public org.eclipse.jdt.internal.compiler.env.IModule getModule(char[] name) {
NameLookup.Answer answer = this.nameLookup.findModule(name);
IModule module = null;
if (answer != null) {
module = NameLookup.getModuleDescriptionInfo(answer.module);
}
return module;
}
@Override
public char[][] getAllAutomaticModules() {
return CharOperation.NO_CHAR_CHAR;
}
@Override
public void applyModuleUpdates(IUpdatableModule module, UpdateKind kind) {
if (this.moduleUpdater != null)
this.moduleUpdater.applyModuleUpdates(module, kind);
}
private IPackageFragmentRoot[] getRootsForOutputLocation(IJavaProject otherJavaProject, IResource outputLocation) throws JavaModelException {
IPath outputPath = outputLocation.getFullPath();
List<IPackageFragmentRoot> result = new ArrayList<>();
if (outputPath.equals(otherJavaProject.getOutputLocation())) {
// collect roots reporting to the default output location:
for (IClasspathEntry classpathEntry : otherJavaProject.getRawClasspath()) {
if (classpathEntry.getOutputLocation() == null) {
for (IPackageFragmentRoot root : otherJavaProject.findPackageFragmentRoots(classpathEntry)) {
IResource rootResource = root.getResource();
if (rootResource == null || !rootResource.getProject().equals(otherJavaProject.getProject()))
continue; // outside this project
result.add(root);
}
}
}
}
if (!result.isEmpty())
return result.toArray(new IPackageFragmentRoot[result.size()]);
// search an entry that specifically (and exclusively) reports to the output location:
for (IClasspathEntry classpathEntry : otherJavaProject.getRawClasspath()) {
if (outputPath.equals(classpathEntry.getOutputLocation()))
return otherJavaProject.findPackageFragmentRoots(classpathEntry);
}
return null;
}
public static IPackageFragmentRoot[] getOwnedPackageFragmentRoots(IJavaProject javaProject) throws JavaModelException {
IPackageFragmentRoot[] allRoots = javaProject.getPackageFragmentRoots();
IPackageFragmentRoot[] sourceRoots = Arrays.copyOf(allRoots, allRoots.length);
int count = 0;
for (int i = 0; i < allRoots.length; i++) {
IPackageFragmentRoot root = allRoots[i];
if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
if(root instanceof JarPackageFragmentRoot) {
// don't treat jars in a project as part of the project's module
continue;
}
IResource resource = root.getResource();
if (resource == null || !resource.getProject().equals(javaProject.getProject()))
continue; // outside this project
}
sourceRoots[count++] = root;
}
if (count < allRoots.length)
return Arrays.copyOf(sourceRoots, count);
return sourceRoots;
}
}