/*
* Copyright 2003-2011 JetBrains s.r.o.
*
* 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 jetbrains.mps.ide.findusages.caches;
import com.intellij.openapi.vfs.VirtualFile;
import jetbrains.mps.extapi.persistence.FileBasedModelRoot;
import jetbrains.mps.extapi.persistence.FolderModelRootBase;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.vfs.VirtualFileUtils;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.project.Project;
import jetbrains.mps.util.annotation.Hack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.module.SRepositoryContentAdapter;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
* Finds indexable roots for a global repository (do not get misled by a project presence here --
* we use project#getRepository which (as for 09.09.16) delegates to the global repo)
*
* Also caches them and clears them on every module change or module added/removed events. [premature optimization?]
*
* AP
*/
final class IndexableRootCalculator {
private final Project myProject;
private final SRepositoryContentAdapter myModuleChangesListener = new SModuleChangesListener();
private final AtomicReference<Set<VirtualFile>> myRootsCache = new AtomicReference<>();
public IndexableRootCalculator(@NotNull com.intellij.openapi.project.Project project) {
myProject = ProjectHelper.fromIdeaProject(project);
}
public void register() {
SRepository repository = myProject.getRepository();
repository.getModelAccess().runReadAction(() -> repository.addRepositoryListener(myModuleChangesListener));
}
public void unregister() {
SRepository repository = myProject.getRepository();
repository.getModelAccess().runReadAction(() -> repository.removeRepositoryListener(myModuleChangesListener));
}
/**
* We are iterating over all modules, visible inside this project including libraries & core modules.
* Thus we provide indices for libs.
* Must be gone when some kind of BootRepository is introduced
*/
@Hack
@NotNull
public Set<VirtualFile> getIndexableRoots() {
Set<VirtualFile> indexableRoots = myRootsCache.get();
if (indexableRoots == null) {
indexableRoots = calcRoots();
myRootsCache.compareAndSet(null, indexableRoots);
}
return indexableRoots;
}
@NotNull
private Set<VirtualFile> calcRoots() {
final Set<VirtualFile> files = new HashSet<VirtualFile>();
myProject.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
for (final SModule m : myProject.getRepository().getModules()) {
for (String path : getIndexablePaths(m)) {
VirtualFile file = VirtualFileUtils.getVirtualFile(path);
if (file != null) {
files.add(file);
}
}
}
}
});
return Collections.unmodifiableSet(files);
}
private static Collection<String> getIndexablePaths(@NotNull SModule module) {
// todo: maybe move getIndexablePaths method to FileBasedModelRoot, or even in ModelRoot classes?
Set<String> result = new HashSet<>();
for (ModelRoot modelRoot : module.getModelRoots()) {
if (modelRoot instanceof FileBasedModelRoot) {
FileBasedModelRoot fileBasedModelRoot = (FileBasedModelRoot) modelRoot;
String contentRoot = fileBasedModelRoot.getContentRoot();
if (contentRoot != null) {
result.add(exposePath(contentRoot));
}
// todo: use excluded & source folders like IDEA
}
if (modelRoot instanceof FolderModelRootBase) {
result.add(exposePath(((FolderModelRootBase) modelRoot).getPath()));
}
}
return result;
}
private static String exposePath(String path) {
String suffix = path.endsWith("." + MPSExtentions.MPS_ARCH) ? "!/" : "";
return path + suffix;
}
private class SModuleChangesListener extends SRepositoryContentAdapter {
private void invalidateCache() {
myRootsCache.set(null);
}
@Override
public void moduleAdded(@NotNull SModule module) {
invalidateCache();
}
@Override
public void moduleRemoved(@NotNull SModuleReference moduleReference) {
invalidateCache();
}
@Override
public void moduleChanged(@NotNull SModule module) {
invalidateCache();
}
}
}