/*
* 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.vfs;
import com.intellij.ProjectTopics;
import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.ApplicationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.roots.impl.DirectoryIndexExcludePolicy;
import com.intellij.openapi.roots.impl.ModuleRootEventImpl;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.BulkFileListener.Adapter;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import jetbrains.mps.project.MPSProject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.module.SRepositoryContentAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Evgeny Gerashchenko
* Date: 30 June 11
*/
public class DirectoryIndexExcludeUpdater extends AbstractProjectComponent {
private final MPSProject myMpsProject;
private MyModuleRepositoryListener myRepositoryListener = new MyModuleRepositoryListener();
private MessageBus myMessageBus;
private MessageBusConnection myConnection;
private BulkFileListener myFSListener = new BulkFileChangesListener();
private DirectoryIndexExcludePolicy[] myExcludePolicies;
private final Object LOCK = new Object();
private boolean myInvalidated = false;
private ApplicationListener myListener = new ApplicationAdapter() {
@Override
public void writeActionFinished(@NotNull Object action) {
synchronized (LOCK) {
if (!myInvalidated) return;
myInvalidated = false;
}
notifyRootsChanged(false);
}
};
public DirectoryIndexExcludeUpdater(Project project, DirectoryIndex directoryIndex, MPSProject mpsProject) {
super(project);
myMpsProject = mpsProject;
myMessageBus = myProject.getMessageBus();
DirectoryIndexExcludePolicy[] allExcludePolicies = Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, myProject);
List<DirectoryIndexExcludePolicy> excludePolicies = new ArrayList<>();
for (DirectoryIndexExcludePolicy ep : allExcludePolicies) {
if (ep instanceof BaseDirectoryIndexExcludePolicy) {
excludePolicies.add(ep);
}
}
myExcludePolicies = excludePolicies.toArray(new DirectoryIndexExcludePolicy[excludePolicies.size()]);
}
@Override
public void initComponent() {
final SRepository repository = getRepository();
repository.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
myRepositoryListener.subscribeTo(repository);
}
});
myConnection = myMessageBus.connect();
myConnection.subscribe(VirtualFileManager.VFS_CHANGES, myFSListener);
ApplicationManager.getApplication().addApplicationListener(myListener);
}
@Override
public void disposeComponent() {
ApplicationManager.getApplication().removeApplicationListener(myListener);
myConnection.disconnect();
final SRepository repository = getRepository();
repository.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
myRepositoryListener.unsubscribeFrom(repository);
}
});
}
private SRepository getRepository() {
return myMpsProject.getRepository();
}
private void notifyRootsChanged(boolean async) {
if (!myProject.isDisposed()) {
if (async) {
synchronized (LOCK) {
myInvalidated = true;
}
} else {
// MPS-24027: send event with beforeRootsChange() to avoid exception in com.intellij.psi.impl.file.impl.PsiVFSListener.MyModuleRootListener
myMessageBus.syncPublisher(ProjectTopics.PROJECT_ROOTS).beforeRootsChange(new ModuleRootEventImpl(myProject, false));
myMessageBus.syncPublisher(ProjectTopics.PROJECT_ROOTS).rootsChanged(new ModuleRootEventImpl(myProject, false));
}
}
}
private boolean isExcluded(VirtualFile dir) {
for (DirectoryIndexExcludePolicy ep : myExcludePolicies) {
if (Arrays.asList(ep.getExcludeRootsForProject()).contains(dir)) {
return true;
}
}
return false;
}
private class BulkFileChangesListener extends Adapter {
@Override
public void after(@NotNull final List<? extends VFileEvent> events) {
for (VFileEvent event : events) {
if (event instanceof VFileCreateEvent) {
VirtualFile file = event.getFile();
if (file != null && file.isDirectory() && isExcluded(file)) {
notifyRootsChanged(false);
}
}
}
}
}
private class MyModuleRepositoryListener extends SRepositoryContentAdapter {
@Override
public void moduleAdded(@NotNull SModule module) {
super.moduleAdded(module);
if (myMpsProject.getProjectModules().contains(module)) {
notifyRootsChanged(true);
}
}
@Override
public void moduleChanged(SModule module) {
super.moduleChanged(module);
if (myMpsProject.getProjectModules().contains(module)) {
notifyRootsChanged(true);
}
}
}
}