/* * Copyright 2003-2016 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.openapi.application.ApplicationManager; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.project.Project; import com.intellij.util.containers.HashMap; import jetbrains.mps.ide.platform.watching.WatchedRoots; import jetbrains.mps.vfs.FileListener; import jetbrains.mps.vfs.FileSystemEvent; import jetbrains.mps.vfs.FileSystemListener; import jetbrains.mps.vfs.IFile; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.util.ProgressMonitor; import java.util.Collection; import java.util.Map; /** * Adds listener to the local file system, which boosts the project reading. * our {@link WatchedRoots} works recursively, for every {@link WatchedRoots#addWatchRequest(String)} call * it triggers the platfrom mechanism which addresses the native file watcher via external process. This procedure is very time-consuming, * thus in order to optimize MPS opening project performance we call it once for the project root recursively, avoiding to call it for each * {@link FileSystemListener#getFileToListen()} as well as {@link FileListener}. * * The other way might be to collect all the files we want to listen and trigger {@link com.intellij.openapi.vfs.LocalFileSystem#addRootsToWatch(Collection, boolean)} * for multiple roots. Unfortunately right now it is not possible since the whole hierarchy (libraries, projects, modules, models, model roots) listens to * FS events directly. Probably the easier way would be to have a SModule as a delegate of the file system events downwards (to model roots and models). * Then we can collect all the module directories on the project opening and call {@code addRootsToWatch()} for them altogether. * Now I see it as the way it should be done. * * AP */ public final class ProjectRootListenerComponent implements ProjectComponent { private static final Logger LOG = LogManager.getLogger(ProjectRootListenerComponent.class); private final IdeaFileSystem myFileSystem; private final Project myProject; private final Map<Project, FileListener> myProject2ListenerMap = new HashMap<>(); private IFile myFile; public ProjectRootListenerComponent(@NotNull IdeaFileSystem fileSystem, Project project) { myFileSystem = fileSystem; myProject = project; } @Override public void initComponent() { String basePath = myProject.getBasePath(); if (basePath!= null) { myFile = myFileSystem.getFile(basePath); EmptyFSListener listener = new EmptyFSListener(); ApplicationManager.getApplication().runReadAction(() -> {myFile.addListener(listener);}); myProject2ListenerMap.put(myProject, listener); } else { LOG.warn("Could not find base path of the project " + myProject); } } @Override public void disposeComponent() { FileListener listener = myProject2ListenerMap.remove(myProject); ApplicationManager.getApplication().runReadAction(() -> {myFile.removeListener(listener);}); } @NotNull @Override public String getComponentName() { return "Project Root Listener"; } @Override public void projectOpened() { } @Override public void projectClosed() { } private static class EmptyFSListener implements FileListener { @Override public String toString() { return "EMPTY LISTENER"; } @Override public void update(ProgressMonitor monitor, @NotNull FileSystemEvent event) { } } }