update copyright
[fedora-idea.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / project / MavenProjectsManagerWatcher.java
blob051e90c9feda498e1c437e63ef5d80574bdfbc08
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.idea.maven.project;
18 import com.intellij.ProjectTopics;
19 import com.intellij.openapi.application.Result;
20 import com.intellij.openapi.application.WriteAction;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.editor.EditorFactory;
23 import com.intellij.openapi.editor.event.DocumentAdapter;
24 import com.intellij.openapi.editor.event.DocumentEvent;
25 import com.intellij.openapi.editor.event.EditorEventMulticaster;
26 import com.intellij.openapi.fileEditor.FileDocumentManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.roots.ModuleRootEvent;
29 import com.intellij.openapi.roots.ModuleRootListener;
30 import com.intellij.openapi.util.Disposer;
31 import com.intellij.openapi.util.io.FileUtil;
32 import com.intellij.openapi.vfs.LocalFileSystem;
33 import com.intellij.openapi.vfs.VfsUtil;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.openapi.vfs.VirtualFileManager;
36 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
37 import com.intellij.openapi.vfs.newvfs.events.*;
38 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
39 import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener;
40 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
41 import com.intellij.openapi.Disposable;
42 import com.intellij.psi.PsiDocumentManager;
43 import com.intellij.util.PathUtil;
44 import com.intellij.util.messages.MessageBusConnection;
45 import com.intellij.util.ui.update.Update;
46 import gnu.trove.THashSet;
47 import org.jetbrains.idea.maven.utils.MavenConstants;
48 import org.jetbrains.idea.maven.utils.MavenMergingUpdateQueue;
49 import org.jetbrains.idea.maven.utils.MavenUtil;
51 import java.io.File;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.Set;
56 public class MavenProjectsManagerWatcher {
57 private static final int DOCUMENT_SAVE_DELAY = 1000;
59 private final Project myProject;
60 private final MavenProjectsTree myProjectsTree;
61 private final MavenGeneralSettings myGeneralSettings;
62 private final MavenProjectsProcessor myReadingProcessor;
63 private final MavenEmbeddersManager myEmbeddersManager;
65 private final List<VirtualFilePointer> mySettingsFilesPointers = new ArrayList<VirtualFilePointer>();
66 private final List<LocalFileSystem.WatchRequest> myWatchedRoots = new ArrayList<LocalFileSystem.WatchRequest>();
68 private final Set<Document> myChangedDocuments = new THashSet<Document>();
69 private final MavenMergingUpdateQueue myChangedDocumentsQueue;
71 public MavenProjectsManagerWatcher(Project project,
72 MavenProjectsTree projectsTree,
73 MavenGeneralSettings generalSettings,
74 MavenProjectsProcessor readingProcessor,
75 MavenEmbeddersManager embeddersManager) {
76 myProject = project;
77 myProjectsTree = projectsTree;
78 myGeneralSettings = generalSettings;
79 myReadingProcessor = readingProcessor;
80 myEmbeddersManager = embeddersManager;
83 myChangedDocumentsQueue = new MavenMergingUpdateQueue(getClass() + ": Document changes queue",
84 DOCUMENT_SAVE_DELAY,
85 false,
86 myProject);
89 public synchronized void start() {
90 final MessageBusConnection myBusConnection = myProject.getMessageBus().connect(myChangedDocumentsQueue);
91 myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new MyFileChangeListener());
92 myBusConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyRootChangesListener());
94 myChangedDocumentsQueue.makeUserAware(myProject);
95 myChangedDocumentsQueue.activate();
97 DocumentAdapter myDocumentListener = new DocumentAdapter() {
98 public void documentChanged(DocumentEvent event) {
99 Document doc = event.getDocument();
100 VirtualFile file = FileDocumentManager.getInstance().getFile(doc);
102 if (file == null) return;
103 boolean isMavenFile =
104 file.getName().equals(MavenConstants.POM_XML) || file.getName().equals(MavenConstants.PROFILES_XML) || isSettingsFile(file);
105 if (!isMavenFile) return;
107 synchronized (myChangedDocuments) {
108 myChangedDocuments.add(doc);
110 myChangedDocumentsQueue.queue(new Update(MavenProjectsManagerWatcher.this) {
111 public void run() {
112 final Set<Document> copy;
114 synchronized (myChangedDocuments) {
115 copy = new THashSet<Document>(myChangedDocuments);
116 myChangedDocuments.clear();
119 MavenUtil.invokeLater(myProject, new Runnable() {
120 public void run() {
121 new WriteAction() {
122 protected void run(Result result) throws Throwable {
123 for (Document each : copy) {
124 PsiDocumentManager.getInstance(myProject).commitDocument(each);
125 FileDocumentManager.getInstance().saveDocument(each);
128 }.execute();
135 getDocumentEventMulticaster().addDocumentListener(myDocumentListener,myBusConnection);
137 final MavenGeneralSettings.Listener mySettingsPathsChangesListener = new MavenGeneralSettings.Listener() {
138 public void pathChanged() {
139 updateSettingsFilePointers();
140 onSettingsChange();
143 myGeneralSettings.addListener(mySettingsPathsChangesListener);
144 Disposer.register(myChangedDocumentsQueue, new Disposable() {
145 public void dispose() {
146 myGeneralSettings.removeListener(mySettingsPathsChangesListener);
149 updateSettingsFilePointers();
152 private void updateSettingsFilePointers() {
153 LocalFileSystem.getInstance().removeWatchedRoots(myWatchedRoots);
154 mySettingsFilesPointers.clear();
155 addFilePointer(myGeneralSettings.getEffectiveUserSettingsIoFile());
156 addFilePointer(myGeneralSettings.getEffectiveGlobalSettingsIoFile());
159 private void addFilePointer(File settingsFile) {
160 if (settingsFile == null) return;
162 myWatchedRoots.add(LocalFileSystem.getInstance().addRootToWatch(getNormalizedPath(settingsFile.getParentFile()), false));
164 String url = VfsUtil.pathToUrl(getNormalizedPath(settingsFile));
165 mySettingsFilesPointers.add(VirtualFilePointerManager.getInstance().create(url, myChangedDocumentsQueue, new VirtualFilePointerListener() {
166 public void beforeValidityChanged(VirtualFilePointer[] pointers) {
169 public void validityChanged(VirtualFilePointer[] pointers) {
171 }));
174 private static String getNormalizedPath(File settingsFile) {
175 String canonized = PathUtil.getCanonicalPath(settingsFile.getAbsolutePath());
176 // todo hook for IDEADEV-40110
177 assert canonized != null : "cannot normalize path for: " + settingsFile;
178 return FileUtil.toSystemIndependentName(canonized);
181 public synchronized void stop() {
182 Disposer.dispose(myChangedDocumentsQueue);
185 private static EditorEventMulticaster getDocumentEventMulticaster() {
186 return EditorFactory.getInstance().getEventMulticaster();
189 public synchronized void addManagedFilesWithProfiles(List<VirtualFile> files, List<String> profiles) {
190 myProjectsTree.addManagedFilesWithProfiles(files, profiles);
191 scheduleUpdateAll();
194 public synchronized void resetManagedFilesAndProfilesInTests(List<VirtualFile> files, List<String> profiles) {
195 myProjectsTree.resetManagedFilesAndProfiles(files, profiles);
196 scheduleUpdateAll();
199 public synchronized void removeManagedFiles(List<VirtualFile> files) {
200 myProjectsTree.removeManagedFiles(files);
201 scheduleUpdateAll();
204 public synchronized void setActiveProfiles(List<String> profiles) {
205 myProjectsTree.setActiveProfiles(profiles);
206 scheduleUpdateAll();
209 private void scheduleUpdateAll() {
210 scheduleUpdateAll(false);
213 private void scheduleUpdateAll(boolean force) {
214 myReadingProcessor.scheduleTask(new MavenProjectsProcessorReadingTask(force, myProjectsTree, myGeneralSettings, null));
217 private void scheduleUpdate(List<VirtualFile> filesToUpdate, List<VirtualFile> filesToDelete) {
218 myReadingProcessor.scheduleTask(new MavenProjectsProcessorReadingTask(filesToUpdate,
219 filesToDelete,
220 false,
221 myProjectsTree,
222 myGeneralSettings,
223 null));
226 private void onSettingsChange() {
227 myEmbeddersManager.reset();
228 scheduleUpdateAll(true);
231 private class MyRootChangesListener implements ModuleRootListener {
232 public void beforeRootsChange(ModuleRootEvent event) {
235 public void rootsChanged(ModuleRootEvent event) {
236 // todo is this logic necessary?
237 List<VirtualFile> existingFiles = myProjectsTree.getProjectsFiles();
238 List<VirtualFile> newFiles = new ArrayList<VirtualFile>();
239 List<VirtualFile> deletedFiles = new ArrayList<VirtualFile>();
241 for (VirtualFile f : myProjectsTree.getExistingManagedFiles()) {
242 if (!existingFiles.contains(f)) {
243 newFiles.add(f);
247 for (VirtualFile f : existingFiles) {
248 if (!f.isValid()) deletedFiles.add(f);
251 scheduleUpdate(newFiles, deletedFiles);
255 private boolean isPomFile(String path) {
256 if (!path.endsWith("/" + MavenConstants.POM_XML)) return false;
257 return myProjectsTree.isPotentialProject(path);
260 private boolean isProfilesFile(String path) {
261 String suffix = "/" + MavenConstants.PROFILES_XML;
262 if (!path.endsWith(suffix)) return false;
263 int pos = path.lastIndexOf(suffix);
264 return myProjectsTree.isPotentialProject(path.substring(0, pos) + "/" + MavenConstants.POM_XML);
267 private boolean isSettingsFile(String path) {
268 for (VirtualFilePointer each : mySettingsFilesPointers) {
269 VirtualFile f = each.getFile();
270 if (f != null && FileUtil.pathsEqual(path, f.getPath())) return true;
272 return false;
275 private boolean isSettingsFile(VirtualFile f) {
276 for (VirtualFilePointer each : mySettingsFilesPointers) {
277 if (each.getFile() == f) return true;
279 return false;
282 private class MyFileChangeListener extends MyFileChangeListenerBase {
283 private List<VirtualFile> filesToUpdate;
284 private List<VirtualFile> filesToRemove;
285 private boolean settingsHaveChanged;
287 protected boolean isRelevant(String path) {
288 return isPomFile(path) || isProfilesFile(path) || isSettingsFile(path);
291 protected void updateFile(VirtualFile file) {
292 doUpdateFile(file, false);
295 protected void deleteFile(VirtualFile file) {
296 doUpdateFile(file, true);
299 private void doUpdateFile(VirtualFile file, boolean remove) {
300 initLists();
302 if (isSettingsFile(file)) {
303 settingsHaveChanged = true;
304 return;
307 VirtualFile pom = getPomFileProfilesFile(file);
308 if (pom != null) {
309 filesToUpdate.add(pom);
310 return;
313 if (remove) {
314 filesToRemove.add(file);
316 else {
317 filesToUpdate.add(file);
321 private VirtualFile getPomFileProfilesFile(VirtualFile f) {
322 if (!f.getName().equals(MavenConstants.PROFILES_XML)) return null;
323 return f.getParent().findChild(MavenConstants.POM_XML);
326 protected void apply() {
327 // the save may occur during project close. in this case the background task
328 // can not be started since the window has already been closed.
329 if (areFileSetsInitialised()) {
330 if (settingsHaveChanged) {
331 onSettingsChange();
333 else {
334 filesToUpdate.removeAll(filesToRemove);
335 scheduleUpdate(filesToUpdate, filesToRemove);
339 clearLists();
342 private boolean areFileSetsInitialised() {
343 return filesToUpdate != null;
346 private void initLists() {
347 // Do not use before() method to initialize the lists
348 // since the listener can be attached during the update
349 // and before method can be skipped.
350 // The better way to fix if, of course, is to do simething with
351 // subscription - add listener not during postStartupActivity
352 // but on project initialization to avoid this situation.
353 if (areFileSetsInitialised()) return;
355 filesToUpdate = new ArrayList<VirtualFile>();
356 filesToRemove = new ArrayList<VirtualFile>();
359 private void clearLists() {
360 filesToUpdate = null;
361 filesToRemove = null;
365 private static abstract class MyFileChangeListenerBase implements BulkFileListener {
366 protected abstract boolean isRelevant(String path);
368 protected abstract void updateFile(VirtualFile file);
370 protected abstract void deleteFile(VirtualFile file);
372 protected abstract void apply();
374 public void before(List<? extends VFileEvent> events) {
375 for (VFileEvent each : events) {
376 if (each instanceof VFileDeleteEvent) {
377 deleteRecursively(((VFileDeleteEvent)each).getFile());
379 else {
380 if (!isRelevant(each.getPath())) continue;
381 if (each instanceof VFilePropertyChangeEvent) {
382 if (((VFilePropertyChangeEvent)each).getPropertyName().equals(VirtualFile.PROP_NAME)) {
383 deleteRecursively(((VFilePropertyChangeEvent)each).getFile());
386 else if (each instanceof VFileMoveEvent) {
387 VFileMoveEvent moveEvent = (VFileMoveEvent)each;
388 String newPath = moveEvent.getNewParent().getPath() + "/" + moveEvent.getFile().getName();
389 if (!isRelevant(newPath)) {
390 deleteRecursively(moveEvent.getFile());
397 private void deleteRecursively(VirtualFile f) {
398 if (isRelevant(f.getPath())) deleteFile(f);
399 if (f.isDirectory()) {
400 for (VirtualFile each : f.getChildren()) {
401 deleteRecursively(each);
406 public void after(List<? extends VFileEvent> events) {
407 for (VFileEvent each : events) {
408 if (!isRelevant(each.getPath())) continue;
410 if (each instanceof VFileCreateEvent) {
411 VFileCreateEvent createEvent = (VFileCreateEvent)each;
412 VirtualFile newChild = createEvent.getParent().findChild(createEvent.getChildName());
413 if (newChild != null) {
414 updateFile(newChild);
417 else if (each instanceof VFileCopyEvent) {
418 VFileCopyEvent copyEvent = (VFileCopyEvent)each;
419 VirtualFile newChild = copyEvent.getNewParent().findChild(copyEvent.getNewChildName());
420 if (newChild != null) {
421 updateFile(newChild);
424 else if (each instanceof VFileContentChangeEvent) {
425 updateFile(((VFileContentChangeEvent)each).getFile());
427 else if (each instanceof VFilePropertyChangeEvent) {
428 if (((VFilePropertyChangeEvent)each).getPropertyName().equals(VirtualFile.PROP_NAME)) {
429 updateFile(((VFilePropertyChangeEvent)each).getFile());
432 else if (each instanceof VFileMoveEvent) {
433 updateFile(((VFileMoveEvent)each).getFile());
436 apply();