/*
* 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.index;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.*;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import org.intellij.erlang.psi.ErlangFile;
import org.intellij.erlang.psi.ErlangModule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class ErlangModuleIndex extends ScalarIndexExtension<String> {
private static final ID<String, Void> ERLANG_MODULE_INDEX = ID.create("ErlangModuleIndex");
private static final int INDEX_VERSION = 1;
private static final EnumeratorStringDescriptor DESCRIPTOR = new EnumeratorStringDescriptor();
@NotNull
private DataIndexer<String, Void, FileContent> myDataIndexer = new MyDataIndexer();
@NotNull
@Override
public ID<String, Void> getName() {
return ERLANG_MODULE_INDEX;
}
@Override
public int getVersion() {
return INDEX_VERSION;
}
@NotNull
@Override
public DataIndexer<String, Void, FileContent> getIndexer() {
return myDataIndexer;
}
@NotNull
@Override
public KeyDescriptor<String> getKeyDescriptor() {
return DESCRIPTOR;
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return ErlangIndexUtil.ERLANG_MODULE_FILTER;
}
@Override
public boolean dependsOnFileContent() {
return false;
}
@NotNull
public static Collection<String> getNames(@NotNull Project project) {
return FileBasedIndex.getInstance().getAllKeys(ERLANG_MODULE_INDEX, project);
}
@NotNull
public static List<ErlangModule> getModulesByName(@NotNull Project project, @NotNull String name, @NotNull GlobalSearchScope searchScope) {
return getByName(project, name, searchScope, new Function<ErlangFile, ErlangModule>() {
@Nullable
@Override
public ErlangModule fun(@NotNull ErlangFile erlangFile) {
return erlangFile.getModule();
}
});
}
@NotNull
public static List<ErlangFile> getFilesByName(@NotNull Project project, @NotNull String name, @NotNull GlobalSearchScope searchScope) {
return getByName(project, name, searchScope, new Function<ErlangFile, ErlangFile>() {
@Override
public ErlangFile fun(ErlangFile erlangFile) {
return erlangFile;
}
});
}
@NotNull
private static <T> List<T> getByName(@NotNull Project project, @NotNull String name, @NotNull GlobalSearchScope searchScope, @NotNull final Function<ErlangFile, T> psiMapper) {
final PsiManager psiManager = PsiManager.getInstance(project);
List<VirtualFile> virtualFiles = getVirtualFilesByName(project, name, searchScope);
return ContainerUtil.mapNotNull(virtualFiles, new Function<VirtualFile, T>() {
@Nullable
@Override
public T fun(@NotNull VirtualFile virtualFile) {
PsiFile psiFile = psiManager.findFile(virtualFile);
return psiFile instanceof ErlangFile ? psiMapper.fun((ErlangFile)psiFile) : null;
}
});
}
@NotNull
private static List<VirtualFile> getVirtualFilesByName(@NotNull Project project, @NotNull String name, @NotNull GlobalSearchScope searchScope) {
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
Collection<VirtualFile> files = FileBasedIndex.getInstance().getContainingFiles(ERLANG_MODULE_INDEX, name, searchScope);
List<VirtualFile> filesList = ContainerUtil.newArrayList(files);
Collections.sort(filesList, new MyProjectFilesComparator(projectFileIndex, searchScope));
return filesList;
}
private static final class MyProjectFilesComparator implements Comparator<VirtualFile> {
private final ProjectFileIndex myProjectFileIndex;
private final GlobalSearchScope mySearchScope;
MyProjectFilesComparator(ProjectFileIndex pfi, GlobalSearchScope searchScope) {
myProjectFileIndex = pfi;
mySearchScope = searchScope;
}
@Override
public int compare(@NotNull VirtualFile f1, @NotNull VirtualFile f2) {
// according to http://www.erlang.org/doc/man/code.html, modules that belong to
// 'kernel' and 'stdlib' applications always appear before any user-defined modules
if (isKernelOrStdlibModule(f1)) return -1;
if (isKernelOrStdlibModule(f2)) return 1;
boolean f1IsInSource = isInSource(f1);
boolean f2IsInSource = isInSource(f2);
if (f1IsInSource != f2IsInSource) return f1IsInSource ? -1 : 1;
boolean f1IsHidden = isUnderHiddenDirectory(f1);
boolean f2IsHidden = isUnderHiddenDirectory(f2);
if (f1IsHidden != f2IsHidden) return f1IsHidden ? 1 : -1;
boolean f1IsInLibrary = isInLibrary(f1);
boolean f2IsInLibrary = isInLibrary(f2);
if (f1IsInLibrary != f2IsInLibrary) return f1IsInLibrary ? -1 : 1;
return f1.getPath().length() - f2.getPath().length();
}
private boolean isKernelOrStdlibModule(@NotNull VirtualFile file) {
VirtualFile kernelAppDir = ErlangApplicationIndex.getApplicationDirectoryByName("kernel", mySearchScope);
if (kernelAppDir != null && VfsUtilCore.isAncestor(kernelAppDir, file, true)) return true;
VirtualFile stdlibAppDir = ErlangApplicationIndex.getApplicationDirectoryByName("stdlib", mySearchScope);
return stdlibAppDir != null && VfsUtilCore.isAncestor(stdlibAppDir, file, true);
}
private boolean isUnderHiddenDirectory(VirtualFile f) {
VirtualFile contentRoot = myProjectFileIndex.getContentRootForFile(f);
while (f != null && (contentRoot == null || VfsUtilCore.isAncestor(contentRoot, f, true))) {
File ioFile = VfsUtilCore.virtualToIoFile(f);
if (ioFile.isHidden()) return true;
f = f.getParent();
}
return false;
}
private boolean isInLibrary(@NotNull VirtualFile file) {
return myProjectFileIndex.isInLibraryClasses(file) || myProjectFileIndex.isInLibrarySource(file);
}
private boolean isInSource(@NotNull VirtualFile file) {
return myProjectFileIndex.isInSource(file);
}
}
private static class MyDataIndexer implements DataIndexer<String, Void, FileContent> {
@Override
@NotNull
public Map<String, Void> map(@NotNull FileContent inputData) {
return Collections.singletonMap(inputData.getFile().getNameWithoutExtension(), null);
}
}
}