Java tutorial
/******************************************************************************* * 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[]) */ @Override public char[][] getModulesDeclaringPackage(char[][] packageName, char[] moduleName) { String[] pkgName = Arrays.stream(packageName).map(String::new).toArray(String[]::new); 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; } @Override public char[][] listPackages(char[] moduleName) { switch (LookupStrategy.get(moduleName)) { case Named: IPackageFragmentRoot[] packageRoots = findModuleContext(moduleName); Set<String> packages = new HashSet<>(); for (IPackageFragmentRoot packageRoot : packageRoots) { try { for (IJavaElement javaElement : packageRoot.getChildren()) { if (javaElement instanceof IPackageFragment && !((IPackageFragment) javaElement).isDefaultPackage()) packages.add(javaElement.getElementName()); } } catch (JavaModelException e) { Util.log(e, "Failed to retrieve packages from " + packageRoot); //$NON-NLS-1$ } } return packages.stream().map(String::toCharArray).toArray(char[][]::new); default: throw new UnsupportedOperationException("can list packages only of a named module"); //$NON-NLS-1$ } } }