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
;
52 import java
.util
.ArrayList
;
53 import java
.util
.List
;
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
) {
77 myProjectsTree
= projectsTree
;
78 myGeneralSettings
= generalSettings
;
79 myReadingProcessor
= readingProcessor
;
80 myEmbeddersManager
= embeddersManager
;
83 myChangedDocumentsQueue
= new MavenMergingUpdateQueue(getClass() + ": Document changes queue",
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) {
112 final Set
<Document
> copy
;
114 synchronized (myChangedDocuments
) {
115 copy
= new THashSet
<Document
>(myChangedDocuments
);
116 myChangedDocuments
.clear();
119 MavenUtil
.invokeLater(myProject
, new Runnable() {
122 protected void run(Result result
) throws Throwable
{
123 for (Document each
: copy
) {
124 PsiDocumentManager
.getInstance(myProject
).commitDocument(each
);
125 FileDocumentManager
.getInstance().saveDocument(each
);
135 getDocumentEventMulticaster().addDocumentListener(myDocumentListener
,myBusConnection
);
137 final MavenGeneralSettings
.Listener mySettingsPathsChangesListener
= new MavenGeneralSettings
.Listener() {
138 public void pathChanged() {
139 updateSettingsFilePointers();
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
) {
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
);
194 public synchronized void resetManagedFilesAndProfilesInTests(List
<VirtualFile
> files
, List
<String
> profiles
) {
195 myProjectsTree
.resetManagedFilesAndProfiles(files
, profiles
);
199 public synchronized void removeManagedFiles(List
<VirtualFile
> files
) {
200 myProjectsTree
.removeManagedFiles(files
);
204 public synchronized void setActiveProfiles(List
<String
> profiles
) {
205 myProjectsTree
.setActiveProfiles(profiles
);
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
,
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
)) {
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;
275 private boolean isSettingsFile(VirtualFile f
) {
276 for (VirtualFilePointer each
: mySettingsFilesPointers
) {
277 if (each
.getFile() == f
) return true;
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
) {
302 if (isSettingsFile(file
)) {
303 settingsHaveChanged
= true;
307 VirtualFile pom
= getPomFileProfilesFile(file
);
309 filesToUpdate
.add(pom
);
314 filesToRemove
.add(file
);
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
) {
334 filesToUpdate
.removeAll(filesToRemove
);
335 scheduleUpdate(filesToUpdate
, filesToRemove
);
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());
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());