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