org.eclipse.jdt.internal.core.SearchableEnvironment.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.core.SearchableEnvironment.java

Source

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