Maven: potential race condition fix
[idea/community.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / project / MavenProjectsManager.java
1 /*
2  * Copyright 2000-2009 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 package org.jetbrains.idea.maven.project;
17
18 import com.intellij.ide.startup.StartupManagerEx;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.Result;
21 import com.intellij.openapi.command.WriteCommandAction;
22 import com.intellij.openapi.components.PersistentStateComponent;
23 import com.intellij.openapi.components.SettingsSavingComponent;
24 import com.intellij.openapi.components.State;
25 import com.intellij.openapi.components.Storage;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.fileEditor.FileDocumentManager;
28 import com.intellij.openapi.fileEditor.FileEditorManager;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.project.DumbAwareRunnable;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.ModuleRootManager;
33 import com.intellij.openapi.roots.ProjectRootManager;
34 import com.intellij.openapi.util.Computable;
35 import com.intellij.openapi.util.Disposer;
36 import com.intellij.openapi.util.Pair;
37 import com.intellij.openapi.util.Ref;
38 import com.intellij.openapi.util.io.FileUtil;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.openapi.vfs.VirtualFileManager;
41 import com.intellij.psi.PsiFile;
42 import com.intellij.psi.PsiManager;
43 import com.intellij.psi.xml.XmlElement;
44 import com.intellij.util.EventDispatcher;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.ui.update.Update;
47 import com.intellij.util.xml.DomElement;
48 import com.intellij.util.xml.reflect.DomCollectionChildDescription;
49 import gnu.trove.THashMap;
50 import gnu.trove.THashSet;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53 import org.jetbrains.annotations.TestOnly;
54 import org.jetbrains.idea.maven.dom.MavenDomUtil;
55 import org.jetbrains.idea.maven.dom.model.MavenDomDependencies;
56 import org.jetbrains.idea.maven.dom.model.MavenDomDependency;
57 import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel;
58 import org.jetbrains.idea.maven.execution.SoutMavenConsole;
59 import org.jetbrains.idea.maven.importing.MavenDefaultModifiableModelsProvider;
60 import org.jetbrains.idea.maven.importing.MavenFoldersImporter;
61 import org.jetbrains.idea.maven.importing.MavenModifiableModelsProvider;
62 import org.jetbrains.idea.maven.importing.MavenProjectImporter;
63 import org.jetbrains.idea.maven.utils.*;
64
65 import java.io.File;
66 import java.io.IOException;
67 import java.util.*;
68 import java.util.concurrent.atomic.AtomicBoolean;
69
70 @State(name = "MavenProjectsManager", storages = {@Storage(id = "default", file = "$PROJECT_FILE$")})
71 public class MavenProjectsManager extends SimpleProjectComponent
72   implements PersistentStateComponent<MavenProjectsManagerState>, SettingsSavingComponent {
73   private static final int IMPORT_DELAY = 1000;
74
75   static final Object SCHEDULE_IMPORT_MESSAGE = "SCHEDULE_IMPORT_MESSAGE";
76   static final Object FORCE_IMPORT_MESSAGE = "FORCE_IMPORT_MESSAGE";
77
78   private final AtomicBoolean isInitialized = new AtomicBoolean();
79
80   private MavenProjectsManagerState myState = new MavenProjectsManagerState();
81
82   private MavenEmbeddersManager myEmbeddersManager;
83
84   private MavenProjectsTree myProjectsTree;
85   private MavenProjectsManagerWatcher myWatcher;
86
87   private MavenProjectsProcessor myReadingProcessor;
88   private MavenProjectsProcessor myResolvingProcessor;
89   private MavenProjectsProcessor myPluginsResolvingProcessor;
90   private MavenProjectsProcessor myFoldersResolvingProcessor;
91   private MavenProjectsProcessor myArtifactsDownloadingProcessor;
92   private MavenProjectsProcessor myPostProcessor;
93
94   private MavenMergingUpdateQueue myImportingQueue;
95   private final Object myImportingDataLock = new Object();
96   private final Map<MavenProject, MavenProjectChanges> myProjectsToImport = new THashMap<MavenProject, MavenProjectChanges>();
97   private boolean myImportModuleGroupsRequired = false;
98
99   private MavenMergingUpdateQueue mySchedulesQueue;
100
101   private final EventDispatcher<MavenProjectsTree.Listener> myProjectsTreeDispatcher =
102     EventDispatcher.create(MavenProjectsTree.Listener.class);
103   private final List<Listener> myManagerListeners = ContainerUtil.createEmptyCOWList();
104
105   public static MavenProjectsManager getInstance(Project p) {
106     return p.getComponent(MavenProjectsManager.class);
107   }
108
109   public MavenProjectsManager(Project project) {
110     super(project);
111   }
112
113   public MavenProjectsManagerState getState() {
114     if (isInitialized()) {
115       applyTreeToState();
116     }
117     return myState;
118   }
119
120   public void loadState(MavenProjectsManagerState state) {
121     myState = state;
122     if (isInitialized()) {
123       applyStateToTree();
124       scheduleUpdateAllProjects(false);
125     }
126   }
127
128   public MavenGeneralSettings getGeneralSettings() {
129     return getWorkspaceSettings().generalSettings;
130   }
131
132   public MavenImportingSettings getImportingSettings() {
133     return getWorkspaceSettings().importingSettings;
134   }
135
136   private MavenWorkspaceSettings getWorkspaceSettings() {
137     return MavenWorkspaceSettingsComponent.getInstance(myProject).getState();
138   }
139
140   public File getLocalRepository() {
141     return getGeneralSettings().getEffectiveLocalRepository();
142   }
143
144   @Override
145   public void initComponent() {
146     if (!isNormalProject()) return;
147
148     StartupManagerEx.getInstanceEx(myProject).registerStartupActivity(new Runnable() {
149       public void run() {
150         boolean wasMavenized = !myState.originalFiles.isEmpty();
151         if (!wasMavenized) return;
152         initMavenized();
153       }
154     });
155   }
156
157   private void initMavenized() {
158     doInit(false);
159   }
160
161   private void initNew(List<VirtualFile> files, List<String> explicitProfiles) {
162     myState.originalFiles = MavenUtil.collectPaths(files);
163     myState.activeProfiles = explicitProfiles;
164     doInit(true);
165   }
166
167   @TestOnly
168   public void initForTests() {
169     doInit(false);
170   }
171
172   private void doInit(final boolean isNew) {
173     synchronized (isInitialized) {
174       if (isInitialized.getAndSet(true)) return;
175
176       initProjectsTree(!isNew);
177
178       initWorkers();
179       listenForSettingsChanges();
180       listenForProjectsTreeChanges();
181
182       MavenUtil.runWhenInitialized(myProject, new DumbAwareRunnable() {
183         public void run() {
184           if (!isUnitTestMode()) {
185             fireActivated();
186             listenForExternalChanges();
187           }
188           scheduleUpdateAllProjects(isNew);
189         }
190       });
191     }
192   }
193
194   private void initProjectsTree(boolean tryToLoadExisting) {
195     if (tryToLoadExisting) {
196       File file = getProjectsTreeFile();
197       try {
198         if (file.exists()) {
199           myProjectsTree = MavenProjectsTree.read(file);
200         }
201       }
202       catch (IOException e) {
203         MavenLog.LOG.info(e);
204       }
205     }
206
207     if (myProjectsTree == null) myProjectsTree = new MavenProjectsTree();
208     applyStateToTree();
209     myProjectsTree.addListener(myProjectsTreeDispatcher.getMulticaster());
210   }
211
212   private void applyTreeToState() {
213     myState.originalFiles = myProjectsTree.getManagedFilesPaths();
214     myState.activeProfiles = new ArrayList<String>(myProjectsTree.getExplicitProfiles());
215     myState.ignoredFiles = new THashSet<String>(myProjectsTree.getIgnoredFilesPaths());
216     myState.ignoredPathMasks = myProjectsTree.getIgnoredFilesPatterns();
217   }
218
219   private void applyStateToTree() {
220     myProjectsTree.resetManagedFilesPathsAndProfiles(myState.originalFiles, myState.activeProfiles);
221     myProjectsTree.setIgnoredFilesPaths(new ArrayList<String>(myState.ignoredFiles));
222     myProjectsTree.setIgnoredFilesPatterns(myState.ignoredPathMasks);
223   }
224
225   public void save() {
226     if (myProjectsTree != null) {
227       try {
228         myProjectsTree.save(getProjectsTreeFile());
229       }
230       catch (IOException e) {
231         MavenLog.LOG.error(e);
232       }
233     }
234   }
235
236   private File getProjectsTreeFile() {
237     File file = new File(getProjectsTreesDir(), myProject.getLocationHash() + "/tree.dat");
238     file.getParentFile().mkdirs();
239     return file;
240   }
241
242   private static File getProjectsTreesDir() {
243     return MavenUtil.getPluginSystemDir("Projects");
244   }
245
246   private void initWorkers() {
247     myEmbeddersManager = new MavenEmbeddersManager(getGeneralSettings());
248
249     myReadingProcessor = new MavenProjectsProcessor(myProject, ProjectBundle.message("maven.reading"), false, myEmbeddersManager);
250     myResolvingProcessor = new MavenProjectsProcessor(myProject, ProjectBundle.message("maven.resolving"), true, myEmbeddersManager);
251     myPluginsResolvingProcessor =
252       new MavenProjectsProcessor(myProject, ProjectBundle.message("maven.downloading.plugins"), true, myEmbeddersManager);
253     myFoldersResolvingProcessor =
254       new MavenProjectsProcessor(myProject, ProjectBundle.message("maven.updating.folders"), true, myEmbeddersManager);
255     myArtifactsDownloadingProcessor =
256       new MavenProjectsProcessor(myProject, ProjectBundle.message("maven.downloading"), true, myEmbeddersManager);
257     myPostProcessor = new MavenProjectsProcessor(myProject, ProjectBundle.message("maven.post.processing"), true, myEmbeddersManager);
258
259     myWatcher = new MavenProjectsManagerWatcher(myProject, myProjectsTree, getGeneralSettings(), myReadingProcessor, myEmbeddersManager);
260
261     myImportingQueue = new MavenMergingUpdateQueue(getComponentName() + ": Importing queue", IMPORT_DELAY, !isUnitTestMode(), myProject);
262     myImportingQueue.setPassThrough(false);
263
264     myImportingQueue.makeUserAware(myProject);
265     myImportingQueue.makeDumbAware(myProject);
266     myImportingQueue.makeModalAware(myProject);
267
268     mySchedulesQueue = new MavenMergingUpdateQueue(getComponentName() + ": Schedules queue", 500, !isUnitTestMode(), myProject);
269     mySchedulesQueue.setPassThrough(false);
270   }
271
272   private void listenForSettingsChanges() {
273     getImportingSettings().addListener(new MavenImportingSettings.Listener() {
274       public void autoImportChanged() {
275         scheduleImportSettings();
276       }
277
278       public void createModuleGroupsChanged() {
279         scheduleImportSettings(true);
280       }
281
282       public void createModuleForAggregatorsChanged() {
283         scheduleImportSettings();
284       }
285     });
286   }
287
288   private void listenForProjectsTreeChanges() {
289     myProjectsTree.addListener(new MavenProjectsTree.ListenerAdapter() {
290       @Override
291       public void projectsIgnoredStateChanged(List<MavenProject> ignored, List<MavenProject> unignored, Object message) {
292         if (message instanceof MavenProjectImporter) return;
293         scheduleImport(false);
294       }
295
296       @Override
297       public void projectsUpdated(List<Pair<MavenProject, MavenProjectChanges>> updated, List<MavenProject> deleted, Object message) {
298         myEmbeddersManager.clearCaches();
299
300         unscheduleAllTasks(deleted);
301
302         List<MavenProject> updatedProjects = MavenUtil.collectFirsts(updated);
303
304         // import only updated and the dependents (we need to update faced-deps, packaging etc);
305         List<Pair<MavenProject, MavenProjectChanges>> toImport = new ArrayList<Pair<MavenProject, MavenProjectChanges>>(updated);
306         for (MavenProject each : updatedProjects) {
307           for (MavenProject eachDependent : myProjectsTree.getDependentProjects(each)) {
308             toImport.add(Pair.create(eachDependent, MavenProjectChanges.DEPENDENCIES));
309           }
310         }
311
312         // resolve updated, theirs dependents, and dependents of deleted
313         Set<MavenProject> toResolve = new THashSet<MavenProject>(updatedProjects);
314         for (MavenProject each : ContainerUtil.concat(updatedProjects, deleted)) {
315           toResolve.addAll(myProjectsTree.getDependentProjects(each));
316         }
317
318         // do not try to resolve projects with syntactic errors
319         Iterator<MavenProject> it = toResolve.iterator();
320         while (it.hasNext()) {
321           MavenProject each = it.next();
322           if (each.hasReadingProblems()) it.remove();
323         }
324
325         if (haveChanges(toImport) || !deleted.isEmpty()) {
326           scheduleImport(toImport, message == FORCE_IMPORT_MESSAGE);
327         }
328         scheduleResolve(toResolve, message == FORCE_IMPORT_MESSAGE);
329       }
330
331       private boolean haveChanges(List<Pair<MavenProject, MavenProjectChanges>> projectsWithChanges) {
332         for (MavenProjectChanges each : MavenUtil.collectSeconds(projectsWithChanges)) {
333           if (each.hasChanges()) return true;
334         }
335         return false;
336       }
337
338       @Override
339       public void projectResolved(Pair<MavenProject, MavenProjectChanges> projectWithChanges,
340                                   org.apache.maven.project.MavenProject nativeMavenProject,
341                                   Object message) {
342         if (shouldScheduleProject(projectWithChanges)) {
343           if (projectWithChanges.first.hasUnresolvedPlugins()) {
344             schedulePluginsResolving(projectWithChanges.first, nativeMavenProject);
345           }
346           scheduleArtifactsDownloading(Collections.singleton(projectWithChanges.first),
347                                        getImportingSettings().shouldDownloadSourcesAutomatically(),
348                                        getImportingSettings().shouldDownloadDocsAutomatically());
349           scheduleForNextImport(projectWithChanges);
350         }
351         processMessage(message);
352       }
353
354       @Override
355       public void foldersResolved(Pair<MavenProject, MavenProjectChanges> projectWithChanges, Object message) {
356         if (shouldScheduleProject(projectWithChanges)) {
357           scheduleForNextImport(projectWithChanges);
358         }
359         processMessage(message);
360       }
361
362       private boolean shouldScheduleProject(Pair<MavenProject, MavenProjectChanges> projectWithChanges) {
363         return !projectWithChanges.first.hasReadingProblems() && projectWithChanges.second.hasChanges();
364       }
365
366       private void processMessage(Object message) {
367         if (getScheduledProjectsCount() == 0) return;
368
369         if (message == SCHEDULE_IMPORT_MESSAGE) {
370           scheduleImport(false);
371         }
372         else if (message == FORCE_IMPORT_MESSAGE) {
373           scheduleImport(true);
374         }
375       }
376     });
377   }
378
379   public void listenForExternalChanges() {
380     myWatcher.start();
381   }
382
383   @Override
384   public void projectClosed() {
385     synchronized (isInitialized) {
386       if (!isInitialized.getAndSet(false)) return;
387
388       Disposer.dispose(mySchedulesQueue);
389       Disposer.dispose(myImportingQueue);
390
391       myWatcher.stop();
392
393       myReadingProcessor.stop();
394       myResolvingProcessor.stop();
395       myPluginsResolvingProcessor.stop();
396       myFoldersResolvingProcessor.stop();
397       myArtifactsDownloadingProcessor.stop();
398       myPostProcessor.stop();
399
400       myEmbeddersManager.release();
401
402       if (isUnitTestMode()) {
403         FileUtil.delete(getProjectsTreesDir());
404       }
405     }
406   }
407
408   @TestOnly
409   public MavenEmbeddersManager getEmbeddersManagerInTests() {
410     return myEmbeddersManager;
411   }
412
413   private boolean isInitialized() {
414     return isInitialized.get();
415   }
416
417   public boolean isMavenizedProject() {
418     return isInitialized();
419   }
420
421   public boolean isMavenizedModule(final Module m) {
422     return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
423       public Boolean compute() {
424         return "true".equals(m.getOptionValue(getMavenizedModuleOptionName()));
425       }
426     });
427   }
428
429   public void setMavenizedModules(Collection<Module> modules, boolean mavenized) {
430     ApplicationManager.getApplication().assertWriteAccessAllowed();
431     for (Module m : modules) {
432       if (mavenized) {
433         m.setOption(getMavenizedModuleOptionName(), "true");
434       }
435       else {
436         m.clearOption(getMavenizedModuleOptionName());
437       }
438     }
439   }
440
441   private String getMavenizedModuleOptionName() {
442     return getComponentName() + ".isMavenModule";
443   }
444
445   @TestOnly
446   public void resetManagedFilesAndProfilesInTests(List<VirtualFile> files, List<String> profiles) {
447     myWatcher.resetManagedFilesAndProfilesInTests(files, profiles);
448   }
449
450   public void addManagedFilesWithProfiles(List<VirtualFile> files, List<String> profiles) {
451     if (!isInitialized()) {
452       initNew(files, profiles);
453     }
454     else {
455       myWatcher.addManagedFilesWithProfiles(files, profiles);
456     }
457   }
458
459   public void addManagedFiles(List<VirtualFile> files) {
460     addManagedFilesWithProfiles(files, Collections.<String>emptyList());
461   }
462
463   public void removeManagedFiles(List<VirtualFile> files) {
464     myWatcher.removeManagedFiles(files);
465   }
466
467   public boolean isManagedFile(VirtualFile f) {
468     if (!isInitialized()) return false;
469     return myProjectsTree.isManagedFile(f);
470   }
471
472   public Collection<String> getExplicitProfiles() {
473     if (!isInitialized()) return Collections.emptyList();
474     return myProjectsTree.getExplicitProfiles();
475   }
476
477   public void setExplicitProfiles(Collection<String> profiles) {
478     myWatcher.setExplicitProfiles(profiles);
479   }
480
481   public Collection<String> getAvailableProfiles() {
482     if (!isInitialized()) return Collections.emptyList();
483     return myProjectsTree.getAvailableProfiles();
484   }
485
486   public Collection<Pair<String, MavenProfileState>> getProfilesWithStates() {
487     if (!isInitialized()) return Collections.emptyList();
488     return myProjectsTree.getProfilesWithStates();
489   }
490
491   public boolean hasProjects() {
492     if (!isInitialized()) return false;
493     return myProjectsTree.hasProjects();
494   }
495
496   public List<MavenProject> getProjects() {
497     if (!isInitialized()) return Collections.emptyList();
498     return myProjectsTree.getProjects();
499   }
500
501   public List<MavenProject> getNonIgnoredProjects() {
502     if (!isInitialized()) return Collections.emptyList();
503     return myProjectsTree.getNonIgnoredProjects();
504   }
505
506   public List<VirtualFile> getProjectsFiles() {
507     if (!isInitialized()) return Collections.emptyList();
508     return myProjectsTree.getProjectsFiles();
509   }
510
511   public MavenProject findProject(VirtualFile f) {
512     if (!isInitialized()) return null;
513     return myProjectsTree.findProject(f);
514   }
515
516   public MavenProject findProject(MavenId id) {
517     if (!isInitialized()) return null;
518     return myProjectsTree.findProject(id);
519   }
520
521   public MavenProject findProject(MavenArtifact artifact) {
522     if (!isInitialized()) return null;
523     return myProjectsTree.findProject(artifact);
524   }
525
526   public MavenProject findProject(Module module) {
527     VirtualFile f = findPomFile(module, new MavenModelsProvider() {
528       public Module[] getModules() {
529         throw new UnsupportedOperationException();
530       }
531
532       public VirtualFile[] getContentRoots(Module module) {
533         return ModuleRootManager.getInstance(module).getContentRoots();
534       }
535     });
536     return f == null ? null : findProject(f);
537   }
538
539   @Nullable
540   public Module findModule(MavenProject project) {
541     if (!isInitialized()) return null;
542     return ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(project.getFile());
543   }
544
545   @NotNull
546   public Set<MavenProject> findInheritors(@Nullable MavenProject parent) {
547     if (parent == null || !isInitialized()) return Collections.emptySet();
548     return myProjectsTree.findInheritors(parent);
549   }
550
551   public MavenProject findContainingProject(VirtualFile file) {
552     if (!isInitialized()) return null;
553     Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file);
554     return module == null ? null : findProject(module);
555   }
556
557   private static VirtualFile findPomFile(Module module, MavenModelsProvider modelsProvider) {
558     for (VirtualFile root : modelsProvider.getContentRoots(module)) {
559       final VirtualFile virtualFile = root.findChild(MavenConstants.POM_XML);
560       if (virtualFile != null) {
561         return virtualFile;
562       }
563     }
564     return null;
565   }
566
567   public MavenProject findAggregator(MavenProject module) {
568     if (!isInitialized()) return null;
569     return myProjectsTree.findAggregator(module);
570   }
571
572   public List<MavenProject> getModules(MavenProject aggregator) {
573     if (!isInitialized()) return Collections.emptyList();
574     return myProjectsTree.getModules(aggregator);
575   }
576
577   public List<String> getIgnoredFilesPaths() {
578     if (!isInitialized()) return Collections.emptyList();
579     return myProjectsTree.getIgnoredFilesPaths();
580   }
581
582   public void setIgnoredFilesPaths(List<String> paths) {
583     if (!isInitialized()) return;
584     myProjectsTree.setIgnoredFilesPaths(paths);
585   }
586
587   public boolean getIgnoredState(MavenProject project) {
588     if (!isInitialized()) return false;
589     return myProjectsTree.getIgnoredState(project);
590   }
591
592   public void setIgnoredState(List<MavenProject> projects, boolean ignored) {
593     if (!isInitialized()) return;
594     myProjectsTree.setIgnoredState(projects, ignored);
595   }
596
597   public List<String> getIgnoredFilesPatterns() {
598     if (!isInitialized()) return Collections.emptyList();
599     return myProjectsTree.getIgnoredFilesPatterns();
600   }
601
602   public void setIgnoredFilesPatterns(List<String> patterns) {
603     if (!isInitialized()) return;
604     myProjectsTree.setIgnoredFilesPatterns(patterns);
605   }
606
607   public boolean isIgnored(MavenProject project) {
608     if (!isInitialized()) return false;
609     return myProjectsTree.isIgnored(project);
610   }
611
612   public Set<MavenRemoteRepository> getRemoteRepositories() {
613     Set<MavenRemoteRepository> result = new THashSet<MavenRemoteRepository>();
614     for (MavenProject each : getProjects()) {
615       for (MavenRemoteRepository eachRepository : each.getRemoteRepositories()) {
616         result.add(eachRepository);
617       }
618     }
619     return result;
620   }
621
622   @TestOnly
623   public MavenProjectsTree getProjectsTreeForTests() {
624     return myProjectsTree;
625   }
626
627   private void scheduleUpdateAllProjects(boolean forceImport) {
628     doScheduleUpdateProjects(null, false, forceImport);
629   }
630
631   public void forceUpdateProjects(Collection<MavenProject> projects) {
632     doScheduleUpdateProjects(projects, true, true);
633   }
634
635   public void forceUpdateAllProjectsOrFindAllAvailablePomFiles() {
636     if (!isMavenizedProject()) {
637       addManagedFiles(collectAllAvailablePomFiles());
638     }
639     doScheduleUpdateProjects(null, true, true);
640   }
641
642   private void doScheduleUpdateProjects(final Collection<MavenProject> projects, final boolean force, final boolean forceImport) {
643     // read when postStartupActivitias start
644     MavenUtil.runWhenInitialized(myProject, new DumbAwareRunnable() {
645       public void run() {
646         if (projects == null) {
647           myWatcher.scheduleUpdateAll(force, forceImport);
648         }
649         else {
650           myWatcher.scheduleUpdate(MavenUtil.collectFiles(projects), Collections.EMPTY_LIST, force, forceImport);
651         }
652       }
653     });
654   }
655
656   private void scheduleResolve(final Collection<MavenProject> projects, final boolean forceImport) {
657     runWhenFullyOpen(new Runnable() {
658       public void run() {
659         Iterator<MavenProject> it = projects.iterator();
660         while (it.hasNext()) {
661           MavenProject each = it.next();
662           Object message = it.hasNext() ? null : (forceImport ? FORCE_IMPORT_MESSAGE : SCHEDULE_IMPORT_MESSAGE);
663           myResolvingProcessor.scheduleTask(new MavenProjectsProcessorResolvingTask(each, myProjectsTree, getGeneralSettings(), message));
664         }
665       }
666     });
667   }
668
669   @TestOnly
670   public void scheduleResolveInTests(Collection<MavenProject> projects) {
671     scheduleResolve(projects, false);
672   }
673
674   @TestOnly
675   public void scheduleResolveAllInTests() {
676     scheduleResolve(getProjects(), false);
677   }
678
679   public void scheduleFoldersResolving(final Collection<MavenProject> projects) {
680     runWhenFullyOpen(new Runnable() {
681       public void run() {
682         Iterator<MavenProject> it = projects.iterator();
683         while (it.hasNext()) {
684           MavenProject each = it.next();
685           Object message = it.hasNext() ? null : FORCE_IMPORT_MESSAGE;
686           myFoldersResolvingProcessor.scheduleTask(
687             new MavenProjectsProcessorFoldersResolvingTask(each, getGeneralSettings(), getImportingSettings(), myProjectsTree, message));
688         }
689       }
690     });
691   }
692
693   public void scheduleFoldersResolvingForAllProjects() {
694     scheduleFoldersResolving(getProjects());
695   }
696
697   private void schedulePluginsResolving(final MavenProject project, final org.apache.maven.project.MavenProject nativeMavenProject) {
698     runWhenFullyOpen(new Runnable() {
699       public void run() {
700         myPluginsResolvingProcessor
701           .scheduleTask(new MavenProjectsProcessorPluginsResolvingTask(project, nativeMavenProject, myProjectsTree));
702       }
703     });
704   }
705
706   public void scheduleArtifactsDownloading(final Collection<MavenProject> projects, final boolean sources, final boolean docs) {
707     if (!sources && !docs) return;
708
709     runWhenFullyOpen(new Runnable() {
710       public void run() {
711         for (MavenProject each : projects) {
712           myArtifactsDownloadingProcessor
713             .scheduleTask(new MavenProjectsProcessorArtifactsDownloadingTask(each, myProjectsTree, sources, docs));
714         }
715       }
716     });
717   }
718
719   public void scheduleArtifactsDownloading(final Collection<MavenProject> projects) {
720     scheduleArtifactsDownloading(projects, true, true);
721   }
722
723   public void scheduleArtifactsDownloadingForAllProjects() {
724     scheduleArtifactsDownloading(getProjects());
725   }
726
727   private void scheduleImport(List<Pair<MavenProject, MavenProjectChanges>> projectsWithChanges, boolean forceImport) {
728     scheduleForNextImport(projectsWithChanges);
729     scheduleImport(forceImport);
730   }
731
732   private void scheduleImportSettings() {
733     scheduleImportSettings(false);
734   }
735
736   private void scheduleImportSettings(boolean importModuleGroupsRequired) {
737     synchronized (myImportingDataLock) {
738       myImportModuleGroupsRequired = importModuleGroupsRequired;
739     }
740     scheduleImport(false);
741   }
742
743   private void scheduleImport(final boolean forceImport) {
744     runWhenFullyOpen(new Runnable() {
745       public void run() {
746         final boolean autoImport = getImportingSettings().isImportAutomatically();
747         // postpone activation to prevent import from being called from events poster
748         mySchedulesQueue.queue(new Update(new Object()) {
749           public void run() {
750             if (autoImport || forceImport) {
751               myImportingQueue.activate();
752             }
753             else {
754               myImportingQueue.deactivate();
755             }
756           }
757         });
758         myImportingQueue.queue(new Update(MavenProjectsManager.this) {
759           public void run() {
760             importProjects();
761             if (!autoImport) myImportingQueue.deactivate();
762           }
763         });
764         if (!forceImport) fireScheduledImportsChanged();
765       }
766     });
767   }
768
769   @TestOnly
770   public void scheduleImportInTests(List<VirtualFile> projectFiles) {
771     List<Pair<MavenProject, MavenProjectChanges>> toImport = new ArrayList<Pair<MavenProject, MavenProjectChanges>>();
772     for (VirtualFile each : projectFiles) {
773       MavenProject project = findProject(each);
774       if (project != null) {
775         toImport.add(Pair.create(project, MavenProjectChanges.ALL));
776       }
777     }
778     scheduleImport(toImport, false);
779   }
780
781   private void scheduleForNextImport(Pair<MavenProject, MavenProjectChanges> projectWithChanges) {
782     scheduleForNextImport(Collections.singletonList(projectWithChanges));
783   }
784
785   private void scheduleForNextImport(List<Pair<MavenProject, MavenProjectChanges>> projectsWithChanges) {
786     synchronized (myImportingDataLock) {
787       for (Pair<MavenProject, MavenProjectChanges> each : projectsWithChanges) {
788         MavenProjectChanges changes = each.second.mergedWith(myProjectsToImport.get(each.first));
789         myProjectsToImport.put(each.first, changes);
790       }
791     }
792   }
793
794   public boolean hasScheduledImports() {
795     if (!isInitialized()) return false;
796     return !myImportingQueue.isEmpty();
797   }
798
799   public int getScheduledProjectsCount() {
800     if (!isInitialized()) return 0;
801     synchronized (myImportingDataLock) {
802       return myProjectsToImport.size();
803     }
804   }
805
806   public void performScheduledImport() {
807     performScheduledImport(true);
808   }
809
810   public void performScheduledImport(final boolean force) {
811     if (!isInitialized()) return;
812     runWhenFullyOpen(new Runnable() {
813       public void run() {
814         // ensure all pending schedules are processed
815         mySchedulesQueue.flush(false);
816         if (!force && !myImportingQueue.isActive()) return;
817         myImportingQueue.flush(false);
818       }
819     });
820   }
821
822   private void runWhenFullyOpen(final Runnable runnable) {
823     if (!isInitialized()) return; // may be called from scheduleImport after project started closing and before it is closed.
824
825     if (isNoBackgroundMode()) {
826       runnable.run();
827       return;
828     }
829
830     final Ref<Runnable> wrapper = new Ref<Runnable>();
831     wrapper.set(new Runnable() {
832       public void run() {
833         if (!StartupManagerEx.getInstanceEx(myProject).postStartupActivityPassed()) {
834           mySchedulesQueue.queue(new Update(runnable) { // should not remove previously schedules tasks
835
836             public void run() {
837               wrapper.get().run();
838             }
839           });
840           return;
841         }
842         runnable.run();
843       }
844     });
845     MavenUtil.runWhenInitialized(myProject, wrapper.get());
846   }
847
848   private void schedulePostImportTasts(List<MavenProjectsProcessorTask> postTasks) {
849     for (MavenProjectsProcessorTask each : postTasks) {
850       myPostProcessor.scheduleTask(each);
851     }
852   }
853
854   private void unscheduleAllTasks(List<MavenProject> projects) {
855     for (MavenProject each : projects) {
856       MavenProjectsProcessorEmptyTask dummyTask = new MavenProjectsProcessorEmptyTask(each);
857
858       synchronized (myImportingDataLock) {
859         myProjectsToImport.remove(each);
860       }
861
862       myResolvingProcessor.removeTask(dummyTask);
863       myPluginsResolvingProcessor.removeTask(dummyTask);
864       myFoldersResolvingProcessor.removeTask(dummyTask);
865       myArtifactsDownloadingProcessor.removeTask(dummyTask);
866       myPostProcessor.removeTask(dummyTask);
867     }
868   }
869
870   @TestOnly
871   public void unscheduleAllTasksInTests() {
872     unscheduleAllTasks(getProjects());
873   }
874
875   public void waitForReadingCompletion() {
876     waitForTasksCompletion(Collections.<MavenProjectsProcessor>emptyList());
877   }
878
879   public void waitForResolvingCompletion() {
880     waitForTasksCompletion(myResolvingProcessor);
881   }
882
883   public void waitForFoldersResolvingCompletion() {
884     waitForTasksCompletion(myFoldersResolvingProcessor);
885   }
886
887   public void waitForPluginsResolvingCompletion() {
888     waitForTasksCompletion(myPluginsResolvingProcessor);
889   }
890
891   public void waitForArtifactsDownloadingCompletion() {
892     waitForTasksCompletion(Arrays.asList(myResolvingProcessor, myArtifactsDownloadingProcessor));
893   }
894
895   public void waitForPostImportTasksCompletion() {
896     myPostProcessor.waitForCompletion();
897   }
898
899   private void waitForTasksCompletion(MavenProjectsProcessor processor) {
900     waitForTasksCompletion(Collections.singletonList(processor));
901   }
902
903   private void waitForTasksCompletion(List<MavenProjectsProcessor> processors) {
904     FileDocumentManager.getInstance().saveAllDocuments();
905
906     myReadingProcessor.waitForCompletion();
907     for (MavenProjectsProcessor each : processors) {
908       each.waitForCompletion();
909     }
910   }
911
912   public void updateProjectTargetFolders() {
913     updateProjectFolders(true);
914   }
915
916   private void updateProjectFolders(final boolean targetFoldersOnly) {
917     MavenUtil.invokeLater(myProject, new Runnable() {
918       public void run() {
919         MavenFoldersImporter.updateProjectFolders(myProject, targetFoldersOnly);
920         VirtualFileManager.getInstance().refresh(false);
921       }
922     });
923   }
924
925   public List<Module> importProjects() {
926     return importProjects(new MavenDefaultModifiableModelsProvider(myProject));
927   }
928
929   public List<Module> importProjects(final MavenModifiableModelsProvider modelsProvider) {
930     final Map<MavenProject, MavenProjectChanges> projectsToImportWithChanges;
931     final boolean importModuleGroupsRequired;
932     synchronized (myImportingDataLock) {
933       projectsToImportWithChanges = new THashMap<MavenProject, MavenProjectChanges>(myProjectsToImport);
934       myProjectsToImport.clear();
935       importModuleGroupsRequired = myImportModuleGroupsRequired;
936     }
937     fireScheduledImportsChanged();
938
939     final Ref<MavenProjectImporter> importer = new Ref<MavenProjectImporter>();
940     final Ref<List<MavenProjectsProcessorTask>> postTasks = new Ref<List<MavenProjectsProcessorTask>>();
941
942     final Runnable r = new Runnable() {
943       public void run() {
944         importer.set(
945           new MavenProjectImporter(myProject, myProjectsTree, getFileToModuleMapping(modelsProvider), projectsToImportWithChanges,
946                                    importModuleGroupsRequired, modelsProvider, getImportingSettings()));
947         postTasks.set(importer.get().importProject());
948       }
949     };
950
951     // called from wizard or ui
952     if (ApplicationManager.getApplication().isDispatchThread()) {
953       r.run();
954     }
955     else {
956       MavenUtil.runInBackground(myProject, ProjectBundle.message("maven.project.importing"), false, new MavenTask() {
957         public void run(MavenProgressIndicator indicator) throws MavenProcessCanceledException {
958           r.run();
959         }
960       }).waitFor();
961     }
962
963
964     VirtualFileManager.getInstance().refresh(isNormalProject());
965     schedulePostImportTasts(postTasks.get());
966
967     // do not block user too often
968     myImportingQueue.restartTimer();
969
970     return importer.get().getCreatedModules();
971   }
972
973   private Map<VirtualFile, Module> getFileToModuleMapping(MavenModelsProvider modelsProvider) {
974     Map<VirtualFile, Module> result = new THashMap<VirtualFile, Module>();
975     for (Module each : modelsProvider.getModules()) {
976       VirtualFile f = findPomFile(each, modelsProvider);
977       if (f != null) result.put(f, each);
978     }
979     return result;
980   }
981
982   private List<VirtualFile> collectAllAvailablePomFiles() {
983     List<VirtualFile> result = new ArrayList<VirtualFile>(getFileToModuleMapping(new MavenDefaultModelsProvider(myProject)).keySet());
984
985     VirtualFile pom = myProject.getBaseDir().findChild(MavenConstants.POM_XML);
986     if (pom != null) result.add(pom);
987
988     return result;
989   }
990
991   public MavenDomDependency addOverridenDependency(final MavenProject mavenProject, final MavenId id) {
992      return addDependency(mavenProject, id, true);
993   }
994
995
996   public MavenDomDependency addDependency(final MavenProject mavenProject, final MavenId id) {
997     return addDependency(mavenProject, id, false);
998   }
999
1000   public MavenDomDependency addDependency(final MavenProject mavenProject, final MavenId id, final boolean overriden) {
1001     final MavenArtifact[] artifact = new MavenArtifact[1];
1002
1003     try {
1004       MavenUtil.run(myProject, "Downloading dependency...", new MavenTask() {
1005         public void run(MavenProgressIndicator indicator) throws MavenProcessCanceledException {
1006           artifact[0] = myProjectsTree.downloadArtifact(mavenProject, id, myEmbeddersManager, new SoutMavenConsole(), indicator);
1007         }
1008       });
1009     }
1010     catch (MavenProcessCanceledException ignore) {
1011       return null;
1012     }
1013
1014     VirtualFile file = mavenProject.getFile();
1015     PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file);
1016
1017     MavenDomDependency result = new WriteCommandAction<MavenDomDependency>(myProject, "Add Maven Dependency", psiFile) {
1018       protected void run(Result<MavenDomDependency> result) throws Throwable {
1019         MavenDomProjectModel model = MavenDomUtil.getMavenDomProjectModel(myProject, mavenProject.getFile());
1020
1021         MavenDomDependency domDependency = MavenDomUtil.createDomDependency(model, artifact[0], getEditor(), overriden);
1022
1023         mavenProject.addDependency(artifact[0]);
1024         result.setResult(domDependency);
1025       }
1026     }.execute().getResultObject();
1027
1028     scheduleImport(Collections.singletonList(Pair.create(mavenProject, MavenProjectChanges.DEPENDENCIES)), true);
1029
1030     return result;
1031   }
1032
1033   @Nullable
1034   private Editor getEditor() {
1035     return FileEditorManager.getInstance(myProject).getSelectedTextEditor();
1036   }
1037
1038    public void addManagerListener(Listener listener) {
1039     myManagerListeners.add(listener);
1040   }
1041
1042   public void addProjectsTreeListener(MavenProjectsTree.Listener listener) {
1043     myProjectsTreeDispatcher.addListener(listener);
1044   }
1045
1046   @TestOnly
1047   public void fireActivatedInTests() {
1048     fireActivated();
1049   }
1050
1051   private void fireActivated() {
1052     for (Listener each : myManagerListeners) {
1053       each.activated();
1054     }
1055   }
1056
1057   private void fireScheduledImportsChanged() {
1058     for (Listener each : myManagerListeners) {
1059       each.scheduledImportsChanged();
1060     }
1061   }
1062
1063   public interface Listener {
1064     void activated();
1065
1066     void scheduledImportsChanged();
1067   }
1068 }