/*
* Copyright 2012-2014 Sergey Ignatov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.erlang.psi.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectAndLibrariesScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.erlang.index.ErlangApplicationIndex;
import org.intellij.erlang.index.ErlangModuleIndex;
import org.intellij.erlang.psi.ErlangModule;
import org.intellij.erlang.psi.ErlangQAtom;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Comparator;
import java.util.List;
public class ErlangModuleReferenceImpl extends ErlangQAtomBasedReferenceImpl {
private static final int COMPARE_NO_RESULT = Integer.MIN_VALUE;
public ErlangModuleReferenceImpl(@NotNull ErlangQAtom element) {
super(element, ErlangPsiImplUtil.getTextRangeForReference(element), ErlangPsiImplUtil.getNameIdentifier(element).getText());
}
@Override
public PsiElement resolveInner() {
GlobalSearchScope scope = getSearchScope();
List<ErlangModule> modules = ErlangModuleIndex.getModulesByName(myElement.getProject(), myReferenceName, scope);
if (modules.size() > 1) {
ContainerUtil.sort(modules, new ModuleResolutionComparator());
}
return ContainerUtil.getFirstItem(modules);
}
@NotNull
@Override
public Object[] getVariants() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@NotNull
private GlobalSearchScope getSearchScope() {
Project project = myElement.getProject();
Module module = ModuleUtilCore.findModuleForPsiElement(myElement);
return module != null ? module.getModuleWithDependenciesAndLibrariesScope(true) :
new ProjectAndLibrariesScope(project);
}
private final class ModuleResolutionComparator implements Comparator<ErlangModule> {
private final GlobalSearchScope myScope;
private final VirtualFile myStdLibDir;
private final VirtualFile myKernelLibDir;
private final ProjectFileIndex myFileIndex;
public ModuleResolutionComparator() {
myScope = getSearchScope();
myStdLibDir = ErlangApplicationIndex.getApplicationDirectoryByName("stdlib", myScope);
myKernelLibDir = ErlangApplicationIndex.getApplicationDirectoryByName("kernel", myScope);
myFileIndex = ProjectRootManager.getInstance(myElement.getProject()).getFileIndex();
}
@Override
public int compare(ErlangModule m1, ErlangModule m2) {
VirtualFile m1File = getVirtualFile(m1);
VirtualFile m2File = getVirtualFile(m2);
if (m1File == null) {
return m2File == null ? 0 : 1;
}
if (m2File == null) {
return -1;
}
// prefer modules from stdlib and kernel (see "Code Path" at http://www.erlang.org/doc/man/code.html)
int byStdLibDir = compareByLibDir(m1File, m2File, myStdLibDir);
if (byStdLibDir != COMPARE_NO_RESULT) {
return byStdLibDir;
}
int byKernelLibDir = compareByLibDir(m1File, m2File, myKernelLibDir);
if (byKernelLibDir != COMPARE_NO_RESULT) {
return byKernelLibDir;
}
// prefer user modules to SDK modules (see "Code Path" at http://www.erlang.org/doc/man/code.html)
boolean m1IsInLib = myFileIndex.isInLibrarySource(m1File);
boolean m2IsInLib = myFileIndex.isInLibrarySource(m2File);
int byBelongingToLib = (m1IsInLib ? 1 : 0) + (m2IsInLib ? -1 : 0);
if (byBelongingToLib != 0) {
return byBelongingToLib;
}
// fallback to comparison by path. Ideally, we should maintain code path and sort according to it.
return FileUtil.comparePaths(m1File.getPath(), m2File.getPath());
}
private int compareByLibDir(@NotNull VirtualFile m1File, @NotNull VirtualFile m2File, @Nullable VirtualFile libDir) {
if (libDir == null) return COMPARE_NO_RESULT;
boolean m1InLib = VfsUtilCore.isAncestor(libDir, m1File, true);
boolean m2InLib = VfsUtilCore.isAncestor(libDir, m2File, true);
if (m1InLib || m2InLib) {
return (m1InLib ? -1 : 0) + (m2InLib ? 1 : 0);
}
return COMPARE_NO_RESULT;
}
@Nullable
private VirtualFile getVirtualFile(@NotNull PsiElement e) {
PsiFile psiFile = e.getContainingFile();
return psiFile != null ? psiFile.getVirtualFile() : null;
}
}
}