additionally guard iterateIndexableFiles / pushedproperty updating as not all process...
[idea/community.git] / platform / lang-impl / src / com / intellij / openapi / roots / impl / PushedFilePropertiesUpdaterImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * @author max
19  */
20 package com.intellij.openapi.roots.impl;
21
22 import com.intellij.ProjectTopics;
23 import com.intellij.concurrency.JobLauncher;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.extensions.ExtensionException;
27 import com.intellij.openapi.extensions.Extensions;
28 import com.intellij.openapi.module.Module;
29 import com.intellij.openapi.module.ModuleManager;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.project.DumbModeTask;
33 import com.intellij.openapi.project.DumbService;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.project.ProjectManager;
36 import com.intellij.openapi.roots.*;
37 import com.intellij.openapi.startup.StartupManager;
38 import com.intellij.openapi.util.Computable;
39 import com.intellij.openapi.util.Condition;
40 import com.intellij.openapi.util.EmptyRunnable;
41 import com.intellij.openapi.util.registry.Registry;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.openapi.vfs.VirtualFileManager;
44 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
45 import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
46 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
47 import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
48 import com.intellij.psi.PsiManager;
49 import com.intellij.psi.impl.PsiManagerEx;
50 import com.intellij.psi.impl.file.impl.FileManagerImpl;
51 import com.intellij.util.Processor;
52 import com.intellij.util.containers.ContainerUtil;
53 import com.intellij.util.indexing.FileBasedIndex;
54 import com.intellij.util.indexing.FileBasedIndexProjectHandler;
55 import com.intellij.util.messages.MessageBusConnection;
56 import com.intellij.util.ui.UIUtil;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import java.io.IOException;
61 import java.util.ArrayList;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.Queue;
65 import java.util.concurrent.ConcurrentLinkedQueue;
66
67 public class PushedFilePropertiesUpdaterImpl extends PushedFilePropertiesUpdater {
68   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater");
69
70   private final Project myProject;
71   private final FilePropertyPusher[] myPushers;
72   private final FilePropertyPusher[] myFilePushers;
73   private final Queue<Runnable> myTasks = new ConcurrentLinkedQueue<Runnable>();
74   private final MessageBusConnection myConnection;
75
76   public PushedFilePropertiesUpdaterImpl(final Project project) {
77     myProject = project;
78     myPushers = Extensions.getExtensions(FilePropertyPusher.EP_NAME);
79     myFilePushers = ContainerUtil.findAllAsArray(myPushers, new Condition<FilePropertyPusher>() {
80       @Override
81       public boolean value(FilePropertyPusher pusher) {
82         return !pusher.pushDirectoriesOnly();
83       }
84     });
85
86     myConnection = project.getMessageBus().connect();
87
88     StartupManager.getInstance(project).registerPreStartupActivity(new Runnable() {
89       @Override
90       public void run() {
91         myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
92           @Override
93           public void rootsChanged(final ModuleRootEvent event) {
94             for (FilePropertyPusher pusher : myPushers) {
95               pusher.afterRootsChanged(project);
96             }
97           }
98         });
99
100         myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
101           @Override
102           public void after(@NotNull List<? extends VFileEvent> events) {
103             List<Runnable> delayedTasks = ContainerUtil.newArrayList();
104             for (VFileEvent event : events) {
105               final VirtualFile file = event.getFile();
106               if (file == null) continue;
107
108               final FilePropertyPusher[] pushers = file.isDirectory() ? myPushers : myFilePushers;
109               if (pushers.length == 0) continue;
110
111               if (event instanceof VFileCreateEvent) {
112                 if (!event.isFromRefresh() || !file.isDirectory()) {
113                   // push synchronously to avoid entering dumb mode in the middle of a meaningful write action
114                   // avoid dumb mode for just one file
115                   doPushRecursively(file, pushers, ProjectRootManager.getInstance(myProject).getFileIndex());
116                 }
117                 else {
118                   ContainerUtil.addIfNotNull(delayedTasks, createRecursivePushTask(file, pushers));
119                 }
120               } else if (event instanceof VFileMoveEvent) {
121                 for (FilePropertyPusher pusher : pushers) {
122                   file.putUserData(pusher.getFileDataKey(), null);
123                 }
124                 // push synchronously to avoid entering dumb mode in the middle of a meaningful write action
125                 doPushRecursively(file, pushers, ProjectRootManager.getInstance(myProject).getFileIndex());
126               }
127             }
128             if (!delayedTasks.isEmpty()) {
129               queueTasks(delayedTasks);
130             }
131           }
132         });
133       }
134     });
135   }
136
137   @Override
138   public void initializeProperties() {
139     for (final FilePropertyPusher pusher : myPushers) {
140       pusher.initExtra(myProject, myProject.getMessageBus(), new FilePropertyPusher.Engine() {
141         @Override
142         public void pushAll() {
143           PushedFilePropertiesUpdaterImpl.this.pushAll(pusher);
144         }
145
146         @Override
147         public void pushRecursively(VirtualFile file, Project project) {
148           queueTasks(ContainerUtil.createMaybeSingletonList(createRecursivePushTask(file, new FilePropertyPusher[]{pusher})));
149         }
150       });
151     }
152   }
153
154   @Override
155   public void pushAllPropertiesNow() {
156     performPushTasks();
157     doPushAll(myPushers);
158   }
159
160   @Nullable
161   private Runnable createRecursivePushTask(final VirtualFile dir, final FilePropertyPusher[] pushers) {
162     if (pushers.length == 0) return null;
163     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
164     if (!fileIndex.isInContent(dir)) return null;
165     return new Runnable() {
166       @Override
167       public void run() {
168         doPushRecursively(dir, pushers, fileIndex);
169       }
170     };
171   }
172
173   private void doPushRecursively(VirtualFile dir, final FilePropertyPusher[] pushers, ProjectFileIndex fileIndex) {
174     fileIndex.iterateContentUnderDirectory(dir, new ContentIterator() {
175       @Override
176       public boolean processFile(final VirtualFile fileOrDir) {
177         applyPushersToFile(fileOrDir, pushers, null);
178         return true;
179       }
180     });
181   }
182
183   private void queueTasks(List<? extends Runnable> actions) {
184     for (Runnable action : actions) {
185       myTasks.offer(action);
186     }
187     final DumbModeTask task = new DumbModeTask() {
188       @Override
189       public void performInDumbMode(@NotNull ProgressIndicator indicator) {
190         performPushTasks();
191       }
192     };
193     myProject.getMessageBus().connect(task).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
194       @Override
195       public void rootsChanged(ModuleRootEvent event) {
196         DumbService.getInstance(myProject).cancelTask(task);
197       }
198     });
199     DumbService.getInstance(myProject).queueTask(task);
200   }
201
202   private void performPushTasks() {
203     boolean hadTasks = false;
204     while (true) {
205       Runnable task = myTasks.poll();
206       if (task == null) {
207         break;
208       }
209       hadTasks = true;
210       task.run();
211     }
212
213     if (hadTasks && !myProject.isDisposed()) {
214       DumbModeTask task = FileBasedIndexProjectHandler.createChangedFilesIndexingTask(myProject);
215       if (task != null) {
216         DumbService.getInstance(myProject).queueTask(task);
217       }
218     }
219   }
220
221   private static <T> T findPusherValuesUpwards(Project project, VirtualFile dir, FilePropertyPusher<T> pusher, T moduleValue) {
222     final T value = pusher.getImmediateValue(project, dir);
223     if (value != null) return value;
224     if (moduleValue != null) return moduleValue;
225     final VirtualFile parent = dir.getParent();
226     if (parent != null) return findPusherValuesUpwards(project, parent, pusher);
227     T projectValue = pusher.getImmediateValue(project, null);
228     return projectValue != null? projectValue : pusher.getDefaultValue();
229   }
230
231   private static <T> T findPusherValuesUpwards(Project project, VirtualFile dir, FilePropertyPusher<T> pusher) {
232     final T userValue = dir.getUserData(pusher.getFileDataKey());
233     if (userValue != null) return userValue;
234     final T value = pusher.getImmediateValue(project, dir);
235     if (value != null) return value;
236     final VirtualFile parent = dir.getParent();
237     if (parent != null) return findPusherValuesUpwards(project, parent, pusher);
238     T projectValue = pusher.getImmediateValue(project, null);
239     return projectValue != null ? projectValue : pusher.getDefaultValue();
240   }
241
242   @Override
243   public void pushAll(final FilePropertyPusher... pushers) {
244     queueTasks(Collections.singletonList(new Runnable() {
245       @Override
246       public void run() {
247         doPushAll(pushers);
248       }
249     }));
250   }
251
252   private void doPushAll(final FilePropertyPusher[] pushers) {
253     Module[] modules = ApplicationManager.getApplication().runReadAction(new Computable<Module[]>() {
254       @Override
255       public Module[] compute() {
256         return ModuleManager.getInstance(myProject).getModules();
257       }
258     });
259
260     List<Runnable> tasks = new ArrayList<Runnable>();
261
262     for (final Module module : modules) {
263       Runnable iteration = ApplicationManager.getApplication().runReadAction(new Computable<Runnable>() {
264         @Override
265         public Runnable compute() {
266           if (module.isDisposed()) return EmptyRunnable.INSTANCE;
267           ProgressManager.checkCanceled();
268
269           final Object[] moduleValues = new Object[pushers.length];
270           for (int i = 0; i < moduleValues.length; i++) {
271             moduleValues[i] = pushers[i].getImmediateValue(module);
272           }
273
274           final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex();
275           return new Runnable() {
276             @Override
277             public void run() {
278               fileIndex.iterateContent(new ContentIterator() {
279                 @Override
280                 public boolean processFile(final VirtualFile fileOrDir) {
281                   applyPushersToFile(fileOrDir, pushers, moduleValues);
282                   return true;
283                 }
284               });
285             }
286           };
287         }
288       });
289       tasks.add(iteration);
290     }
291
292     if (ourConcurrentlyFlag.get() == Boolean.TRUE && Registry.is("idea.concurrent.scanning.files.to.index")) {
293       JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tasks, null, false, new Processor<Runnable>() {
294         @Override
295         public boolean process(Runnable runnable) {
296           runnable.run();
297           return true;
298         }
299       });
300     } else {
301       for(Runnable r:tasks) r.run();
302     }
303   }
304
305   public static final ThreadLocal<Boolean> ourConcurrentlyFlag = new ThreadLocal<Boolean>();
306
307   private void applyPushersToFile(final VirtualFile fileOrDir, final FilePropertyPusher[] pushers, final Object[] moduleValues) {
308     ApplicationManager.getApplication().runReadAction(new Runnable() {
309       @Override
310       public void run() {
311         ProgressManager.checkCanceled();
312         if (!fileOrDir.isValid()) return;
313         doApplyPushersToFile(fileOrDir, pushers, moduleValues);
314       }
315     });
316   }
317   private void doApplyPushersToFile(VirtualFile fileOrDir, FilePropertyPusher[] pushers, Object[] moduleValues) {
318     FilePropertyPusher<Object> pusher = null;
319     try {
320       final boolean isDir = fileOrDir.isDirectory();
321       for (int i = 0, pushersLength = pushers.length; i < pushersLength; i++) {
322         //noinspection unchecked
323         pusher = pushers[i];
324         if (!isDir && (pusher.pushDirectoriesOnly() || !pusher.acceptsFile(fileOrDir)) || isDir && !pusher.acceptsDirectory(fileOrDir, myProject)) {
325           continue;
326         }
327         findAndUpdateValue(fileOrDir, pusher, moduleValues != null ? moduleValues[i] : null);
328       }
329     }
330     catch (AbstractMethodError ame) { // acceptsDirectory is missed
331       if (pusher != null) throw new ExtensionException(pusher.getClass());
332       throw ame;
333     }
334   }
335
336   @Override
337   public <T> void findAndUpdateValue(final VirtualFile fileOrDir, final FilePropertyPusher<T> pusher, final T moduleValue) {
338     final T value = findPusherValuesUpwards(myProject, fileOrDir, pusher, moduleValue);
339     updateValue(myProject, fileOrDir, value, pusher);
340   }
341
342   private static <T> void updateValue(final Project project, final VirtualFile fileOrDir, final T value, final FilePropertyPusher<T> pusher) {
343     final T oldValue = fileOrDir.getUserData(pusher.getFileDataKey());
344     if (value != oldValue) {
345       fileOrDir.putUserData(pusher.getFileDataKey(), value);
346       try {
347         pusher.persistAttribute(project, fileOrDir, value);
348       }
349       catch (IOException e) {
350         LOG.error(e);
351       }
352     }
353   }
354
355   @Override
356   public void filePropertiesChanged(@NotNull final VirtualFile file) {
357     ApplicationManager.getApplication().assertReadAccessAllowed();
358     FileBasedIndex.getInstance().requestReindex(file);
359     for (final Project project : ProjectManager.getInstance().getOpenProjects()) {
360       reloadPsi(file, project);
361     }
362   }
363
364   private static void reloadPsi(final VirtualFile file, final Project project) {
365     final FileManagerImpl fileManager = (FileManagerImpl)((PsiManagerEx)PsiManager.getInstance(project)).getFileManager();
366     if (fileManager.findCachedViewProvider(file) != null) {
367       UIUtil.invokeLaterIfNeeded(new Runnable() {
368         @Override
369         public void run() {
370           if (project.isDisposed()) {
371             return;
372           }
373           ApplicationManager.getApplication().runWriteAction(new Runnable() {
374             @Override
375             public void run() {
376               fileManager.forceReload(file);
377             }
378           });
379         }
380       });
381     }
382   }
383
384   @Override
385   public void processPendingEvents() {
386     myConnection.deliverImmediately();
387   }
388 }