summaryrefslogtreecommitdiff
path: root/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java')
-rw-r--r--platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java351
1 files changed, 351 insertions, 0 deletions
diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java
new file mode 100644
index 000000000000..a96432e2b64c
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdaterImpl.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2000-2014 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.
+ */
+
+/*
+ * @author max
+ */
+package com.intellij.openapi.roots.impl;
+
+import com.intellij.ProjectTopics;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.ExtensionException;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.DumbModeTask;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.roots.*;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.EmptyRunnable;
+import com.intellij.openapi.vfs.*;
+import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.impl.PsiManagerEx;
+import com.intellij.psi.impl.file.impl.FileManagerImpl;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.indexing.FileBasedIndex;
+import com.intellij.util.indexing.FileBasedIndexProjectHandler;
+import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class PushedFilePropertiesUpdaterImpl extends PushedFilePropertiesUpdater {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater");
+
+ private final Project myProject;
+ private final FilePropertyPusher[] myPushers;
+ private final FilePropertyPusher[] myFilePushers;
+ private final Queue<Runnable> myTasks = new ConcurrentLinkedQueue<Runnable>();
+ private final MessageBusConnection myConnection;
+
+ public PushedFilePropertiesUpdaterImpl(final Project project) {
+ myProject = project;
+ myPushers = Extensions.getExtensions(FilePropertyPusher.EP_NAME);
+ myFilePushers = ContainerUtil.findAllAsArray(myPushers, new Condition<FilePropertyPusher>() {
+ @Override
+ public boolean value(FilePropertyPusher pusher) {
+ return !pusher.pushDirectoriesOnly();
+ }
+ });
+
+ myConnection = project.getMessageBus().connect();
+
+ StartupManager.getInstance(project).registerPreStartupActivity(new Runnable() {
+ @Override
+ public void run() {
+ myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
+ @Override
+ public void rootsChanged(final ModuleRootEvent event) {
+ for (FilePropertyPusher pusher : myPushers) {
+ pusher.afterRootsChanged(project);
+ }
+ }
+ });
+
+ myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new VirtualFileAdapter() {
+ @Override
+ public void fileCreated(@NotNull final VirtualFileEvent event) {
+ final VirtualFile file = event.getFile();
+ final FilePropertyPusher[] pushers = file.isDirectory() ? myPushers : myFilePushers;
+ if (!event.isFromRefresh() || !file.isDirectory()) {
+ // push synchronously to avoid entering dumb mode in the middle of a meaningful write action
+ // avoid dumb mode for just one file
+ doPushRecursively(file, pushers, ProjectRootManager.getInstance(myProject).getFileIndex());
+ }
+ else {
+ schedulePushRecursively(file, pushers);
+ }
+ }
+
+ @Override
+ public void fileMoved(@NotNull final VirtualFileMoveEvent event) {
+ final VirtualFile file = event.getFile();
+ final FilePropertyPusher[] pushers = file.isDirectory() ? myPushers : myFilePushers;
+ if (pushers.length == 0) return;
+ for (FilePropertyPusher pusher : pushers) {
+ file.putUserData(pusher.getFileDataKey(), null);
+ }
+ // push synchronously to avoid entering dumb mode in the middle of a meaningful write action
+ doPushRecursively(file, pushers, ProjectRootManager.getInstance(myProject).getFileIndex());
+ }
+ }));
+ }
+ });
+ }
+
+ @Override
+ public void initializeProperties() {
+ for (final FilePropertyPusher pusher : myPushers) {
+ pusher.initExtra(myProject, myProject.getMessageBus(), new FilePropertyPusher.Engine() {
+ @Override
+ public void pushAll() {
+ PushedFilePropertiesUpdaterImpl.this.pushAll(pusher);
+ }
+
+ @Override
+ public void pushRecursively(VirtualFile file, Project project) {
+ PushedFilePropertiesUpdaterImpl.this.schedulePushRecursively(file, pusher);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void pushAllPropertiesNow() {
+ performPushTasks();
+ doPushAll(myPushers);
+ }
+
+ private void schedulePushRecursively(final VirtualFile dir, final FilePropertyPusher... pushers) {
+ if (pushers.length == 0) return;
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ if (!fileIndex.isInContent(dir)) return;
+ queueTask(new Runnable() {
+ @Override
+ public void run() {
+ doPushRecursively(dir, pushers, fileIndex);
+ }
+ });
+ }
+
+ private void doPushRecursively(VirtualFile dir, final FilePropertyPusher[] pushers, ProjectFileIndex fileIndex) {
+ fileIndex.iterateContentUnderDirectory(dir, new ContentIterator() {
+ @Override
+ public boolean processFile(final VirtualFile fileOrDir) {
+ applyPushersToFile(fileOrDir, pushers, null);
+ return true;
+ }
+ });
+ }
+
+ private void queueTask(Runnable action) {
+ myTasks.offer(action);
+ final DumbModeTask task = new DumbModeTask() {
+ @Override
+ public void performInDumbMode(@NotNull ProgressIndicator indicator) {
+ performPushTasks();
+ }
+ };
+ myProject.getMessageBus().connect(task).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
+ @Override
+ public void rootsChanged(ModuleRootEvent event) {
+ DumbService.getInstance(myProject).cancelTask(task);
+ }
+ });
+ DumbService.getInstance(myProject).queueTask(task);
+ }
+
+ private void performPushTasks() {
+ boolean hadTasks = false;
+ while (true) {
+ Runnable task = myTasks.poll();
+ if (task == null) {
+ break;
+ }
+ hadTasks = true;
+ task.run();
+ }
+
+ if (hadTasks && !myProject.isDisposed()) {
+ DumbModeTask task = FileBasedIndexProjectHandler.createChangedFilesIndexingTask(myProject);
+ if (task != null) {
+ DumbService.getInstance(myProject).queueTask(task);
+ }
+ }
+ }
+
+ private static <T> T findPusherValuesUpwards(Project project, VirtualFile dir, FilePropertyPusher<T> pusher, T moduleValue) {
+ final T value = pusher.getImmediateValue(project, dir);
+ if (value != null) return value;
+ if (moduleValue != null) return moduleValue;
+ final VirtualFile parent = dir.getParent();
+ if (parent != null) return findPusherValuesUpwards(project, parent, pusher);
+ T projectValue = pusher.getImmediateValue(project, null);
+ return projectValue != null? projectValue : pusher.getDefaultValue();
+ }
+
+ private static <T> T findPusherValuesUpwards(Project project, VirtualFile dir, FilePropertyPusher<T> pusher) {
+ final T userValue = dir.getUserData(pusher.getFileDataKey());
+ if (userValue != null) return userValue;
+ final T value = pusher.getImmediateValue(project, dir);
+ if (value != null) return value;
+ final VirtualFile parent = dir.getParent();
+ if (parent != null) return findPusherValuesUpwards(project, parent, pusher);
+ T projectValue = pusher.getImmediateValue(project, null);
+ return projectValue != null ? projectValue : pusher.getDefaultValue();
+ }
+
+ @Override
+ public void pushAll(final FilePropertyPusher... pushers) {
+ queueTask(new Runnable() {
+ @Override
+ public void run() {
+ doPushAll(pushers);
+ }
+ });
+ }
+
+ private void doPushAll(final FilePropertyPusher[] pushers) {
+ Module[] modules = ApplicationManager.getApplication().runReadAction(new Computable<Module[]>() {
+ @Override
+ public Module[] compute() {
+ return ModuleManager.getInstance(myProject).getModules();
+ }
+ });
+
+ for (final Module module : modules) {
+ Runnable iteration = ApplicationManager.getApplication().runReadAction(new Computable<Runnable>() {
+ @Override
+ public Runnable compute() {
+ if (module.isDisposed()) return EmptyRunnable.INSTANCE;
+ ProgressManager.checkCanceled();
+
+ final Object[] moduleValues = new Object[pushers.length];
+ for (int i = 0; i < moduleValues.length; i++) {
+ moduleValues[i] = pushers[i].getImmediateValue(module);
+ }
+
+ final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex();
+ return new Runnable() {
+ @Override
+ public void run() {
+ fileIndex.iterateContent(new ContentIterator() {
+ @Override
+ public boolean processFile(final VirtualFile fileOrDir) {
+ applyPushersToFile(fileOrDir, pushers, moduleValues);
+ return true;
+ }
+ });
+ }
+ };
+ }
+ });
+ iteration.run();
+ }
+ }
+
+ private void applyPushersToFile(final VirtualFile fileOrDir, final FilePropertyPusher[] pushers, final Object[] moduleValues) {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void run() {
+ ProgressManager.checkCanceled();
+ if (!fileOrDir.isValid()) return;
+ doApplyPushersToFile(fileOrDir, pushers, moduleValues);
+ }
+ });
+ }
+ private void doApplyPushersToFile(VirtualFile fileOrDir, FilePropertyPusher[] pushers, Object[] moduleValues) {
+ FilePropertyPusher<Object> pusher = null;
+ try {
+ final boolean isDir = fileOrDir.isDirectory();
+ for (int i = 0, pushersLength = pushers.length; i < pushersLength; i++) {
+ //noinspection unchecked
+ pusher = pushers[i];
+ if (!isDir && (pusher.pushDirectoriesOnly() || !pusher.acceptsFile(fileOrDir))) continue;
+ else if (isDir && !pusher.acceptsDirectory(fileOrDir, myProject)) continue;
+ findAndUpdateValue(fileOrDir, pusher, moduleValues != null ? moduleValues[i]:null);
+ }
+ }
+ catch (AbstractMethodError ame) { // acceptsDirectory is missed
+ if (pusher != null) throw new ExtensionException(pusher.getClass());
+ throw ame;
+ }
+ }
+
+ @Override
+ public <T> void findAndUpdateValue(final VirtualFile fileOrDir, final FilePropertyPusher<T> pusher, final T moduleValue) {
+ final T value = findPusherValuesUpwards(myProject, fileOrDir, pusher, moduleValue);
+ updateValue(myProject, fileOrDir, value, pusher);
+ }
+
+ private static <T> void updateValue(final Project project, final VirtualFile fileOrDir, final T value, final FilePropertyPusher<T> pusher) {
+ final T oldValue = fileOrDir.getUserData(pusher.getFileDataKey());
+ if (value != oldValue) {
+ fileOrDir.putUserData(pusher.getFileDataKey(), value);
+ try {
+ pusher.persistAttribute(project, fileOrDir, value);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ }
+
+ @Override
+ public void filePropertiesChanged(@NotNull final VirtualFile file) {
+ ApplicationManager.getApplication().assertReadAccessAllowed();
+ FileBasedIndex.getInstance().requestReindex(file);
+ for (final Project project : ProjectManager.getInstance().getOpenProjects()) {
+ reloadPsi(file, project);
+ }
+ }
+
+ private static void reloadPsi(final VirtualFile file, final Project project) {
+ final FileManagerImpl fileManager = (FileManagerImpl)((PsiManagerEx)PsiManager.getInstance(project)).getFileManager();
+ if (fileManager.findCachedViewProvider(file) != null) {
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ if (project.isDisposed()) {
+ return;
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ fileManager.forceReload(file);
+ }
+ });
+ }
+ });
+ }
+ }
+
+ @Override
+ public void processPendingEvents() {
+ myConnection.deliverImmediately();
+ }
+}