package org.elixir_lang.reference;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.util.Function;
import org.elixir_lang.psi.NamedElement;
import org.elixir_lang.psi.QualifiableAlias;
import org.elixir_lang.psi.scope.module.MultiResolve;
import org.elixir_lang.psi.scope.module.Variants;
import org.elixir_lang.psi.stub.index.AllName;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.elixir_lang.reference.module.ResolvableName.resolvableName;
public class Module extends PsiReferenceBase<QualifiableAlias> implements PsiPolyVariantReference {
/*
*
* Static Methods
*
*/
/*
* Public Static Methods
*/
@NotNull
private PsiElement maxScope;
/*
* Private Static Methods
*/
public Module(@NotNull QualifiableAlias qualifiableAlias, @NotNull PsiElement maxScope) {
super(qualifiableAlias, TextRange.create(0, qualifiableAlias.getTextLength()));
this.maxScope = maxScope;
}
/**
* Iterates over each navigation element for the PsiElements with {@code name} in {@code project}.
*
* @param project Whose index to search for {@code name}
* @param name Name to search for in {@code project} StubIndex
* @param function return {@code false} to stop iteration early
* @return {@code true} if all calls to {@code function} returned {@code true}
*/
public static boolean forEachNavigationElement(@NotNull Project project,
@NotNull String name,
@NotNull Function<PsiElement, Boolean> function) {
Collection<NamedElement> namedElementCollection = namedElementCollection(project, name);
return forEachNavigationElement(namedElementCollection, function);
}
/*
* Fields
*/
/**
* Iterates over each navigation element for the PsiElements in {@code psiElementCollection}.
*
* @param psiElementCollection Collection of PsiElements that aren't guaranteed to be navigation elements, such as
* the binary elements in {@code .beam} files.
* @param function Return {@code false} to stop processing and abandon enumeration early
* @return {@code true} if all calls to {@code function} returned {@code true}
*/
private static boolean forEachNavigationElement(@NotNull Collection<? extends PsiElement> psiElementCollection,
@NotNull Function<PsiElement, Boolean> function) {
boolean keepProcessing = true;
for (PsiElement psiElement : psiElementCollection) {
/* The psiElement may be a ModuleImpl from a .beam. Using #getNaviationElement() ensures a source
(either true source or decompiled) is used. */
keepProcessing = function.fun(psiElement.getNavigationElement());
if (!keepProcessing) {
break;
}
}
return keepProcessing;
}
/*
* Constructors
*/
private static Collection<NamedElement> namedElementCollection(@NotNull Project project, @NotNull String name) {
Collection<NamedElement> namedElementCollection;
if (DumbService.isDumb(project)) {
namedElementCollection = Collections.emptyList();
} else {
namedElementCollection = StubIndex.getElements(
AllName.KEY,
name,
project,
GlobalSearchScope.allScope(project),
NamedElement.class
);
}
return namedElementCollection;
}
/*
*
* Instance Methods
*
*/
/*
* Public Instance Methods
*/
@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
List<ResolveResult> resolveResultList = null;
final String name = resolvableName(myElement);
if (name != null) {
resolveResultList = MultiResolve.resolveResultList(name, incompleteCode, myElement, maxScope);
if (resolveResultList == null || resolveResultList.isEmpty()) {
resolveResultList = multiResolveProject(
myElement.getProject(),
name
);
}
}
ResolveResult[] resolveResults;
if (resolveResultList == null) {
resolveResults = new ResolveResult[0];
} else {
resolveResults = resolveResultList.toArray(new ResolveResult[resolveResultList.size()]);
}
return resolveResults;
}
/*
* Private Instance Methods
*/
private List<ResolveResult> multiResolveProject(@NotNull Project project,
@NotNull String name) {
List<ResolveResult> results = new ArrayList<ResolveResult>();
forEachNavigationElement(project, name, new Function<PsiElement, Boolean>() {
@Override
public Boolean fun(PsiElement navigationElement) {
results.add(new PsiElementResolveResult(navigationElement));
return true;
}
});
return results;
}
@Nullable
@Override
public PsiElement resolve() {
ResolveResult[] resolveResults = multiResolve(false);
return resolveResults.length == 1 ? resolveResults[0].getElement() : null;
}
@NotNull
@Override
public Object[] getVariants() {
List<LookupElement> lookupElementList = Variants.lookupElementList(myElement);
return lookupElementList.toArray(new Object[lookupElementList.size()]);
}
}