check for timers, startup activities and listeners disposal
[fedora-idea.git] / platform-impl / src / com / intellij / openapi / project / impl / ProjectManagerImpl.java
blob066db43d3bb8138b07a71cdcd595852188ec5465
1 package com.intellij.openapi.project.impl;
3 import com.intellij.ide.AppLifecycleListener;
4 import com.intellij.ide.highlighter.WorkspaceFileType;
5 import com.intellij.ide.impl.ProjectUtil;
6 import com.intellij.ide.startup.impl.StartupManagerImpl;
7 import com.intellij.openapi.application.Application;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.application.ModalityState;
10 import com.intellij.openapi.application.PathManager;
11 import com.intellij.openapi.application.ex.ApplicationEx;
12 import com.intellij.openapi.application.ex.ApplicationManagerEx;
13 import com.intellij.openapi.application.impl.ApplicationImpl;
14 import com.intellij.openapi.components.ExportableApplicationComponent;
15 import com.intellij.openapi.components.StateStorage;
16 import com.intellij.openapi.components.impl.stores.IComponentStore;
17 import com.intellij.openapi.components.impl.stores.IProjectStore;
18 import com.intellij.openapi.components.impl.stores.XmlElementStorage;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.fileEditor.FileDocumentManager;
21 import com.intellij.openapi.progress.ProcessCanceledException;
22 import com.intellij.openapi.progress.ProgressIndicator;
23 import com.intellij.openapi.progress.ProgressManager;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.project.ProjectBundle;
26 import com.intellij.openapi.project.ProjectManagerListener;
27 import com.intellij.openapi.project.ProjectReloadState;
28 import com.intellij.openapi.project.ex.ProjectEx;
29 import com.intellij.openapi.project.ex.ProjectManagerEx;
30 import com.intellij.openapi.startup.StartupManager;
31 import com.intellij.openapi.ui.Messages;
32 import com.intellij.openapi.util.*;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.vfs.LocalFileSystem;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.openapi.vfs.VirtualFileEvent;
37 import com.intellij.openapi.vfs.VirtualFileManagerListener;
38 import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
39 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
40 import com.intellij.util.Alarm;
41 import com.intellij.util.ProfilingUtil;
42 import com.intellij.util.containers.ContainerUtil;
43 import com.intellij.util.containers.HashMap;
44 import com.intellij.util.io.fs.IFile;
45 import com.intellij.util.messages.MessageBus;
46 import com.intellij.util.messages.MessageBusConnection;
47 import gnu.trove.TObjectLongHashMap;
48 import org.jdom.Element;
49 import org.jdom.JDOMException;
50 import org.jetbrains.annotations.NonNls;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53 import org.jetbrains.annotations.TestOnly;
55 import java.io.File;
56 import java.io.IOException;
57 import java.util.*;
59 public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExternalizable, ExportableApplicationComponent {
60 private static final boolean LOG_PROJECT_LEAKAGE_IN_TESTS = false;
61 private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectManagerImpl");
62 public static final int CURRENT_FORMAT_VERSION = 4;
64 private static final Key<ArrayList<ProjectManagerListener>> LISTENERS_IN_PROJECT_KEY = Key.create("LISTENERS_IN_PROJECT_KEY");
65 @NonNls private static final String ELEMENT_DEFAULT_PROJECT = "defaultProject";
67 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
68 private ProjectImpl myDefaultProject; // Only used asynchronously in save and dispose, which itself are synchronized.
70 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
71 private Element myDefaultProjectRootElement; // Only used asynchronously in save and dispose, which itself are synchronized.
73 private final ArrayList<Project> myOpenProjects = new ArrayList<Project>();
74 private final List<ProjectManagerListener> myListeners = ContainerUtil.createEmptyCOWList();
76 private Project myCurrentTestProject = null;
78 private final Map<VirtualFile, byte[]> mySavedCopies = new HashMap<VirtualFile, byte[]>();
79 private final TObjectLongHashMap<VirtualFile> mySavedTimestamps = new TObjectLongHashMap<VirtualFile>();
80 private final HashMap<Project, List<Pair<VirtualFile, StateStorage>>> myChangedProjectFiles = new HashMap<Project, List<Pair<VirtualFile, StateStorage>>>();
81 private final Alarm myChangedFilesAlarm = new Alarm();
82 private final List<Pair<VirtualFile, StateStorage>> myChangedApplicationFiles = new ArrayList<Pair<VirtualFile, StateStorage>>();
83 private volatile int myReloadBlockCount = 0;
84 private final Map<Project, String> myProjects = new WeakHashMap<Project, String>();
85 private static final int MAX_LEAKY_PROJECTS = 42;
87 private static ProjectManagerListener[] getListeners(Project project) {
88 ArrayList<ProjectManagerListener> array = project.getUserData(LISTENERS_IN_PROJECT_KEY);
89 if (array == null) return ProjectManagerListener.EMPTY_ARRAY;
90 return array.toArray(new ProjectManagerListener[array.size()]);
93 public ProjectManagerImpl(VirtualFileManagerEx virtualFileManagerEx) {
94 Application app = ApplicationManager.getApplication();
95 MessageBus messageBus = app.getMessageBus();
96 MessageBusConnection connection = messageBus.connect(app);
97 connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() {
98 public void storageFileChanged(final VirtualFileEvent event, @NotNull final StateStorage storage) {
99 VirtualFile file = event.getFile();
100 if (!file.isDirectory()) {
101 if (!((ApplicationImpl)ApplicationManager.getApplication()).isSaving() && !(event.getRequestor() instanceof StateStorage.SaveSession)) {
102 saveChangedProjectFile(file, null, storage);
108 addProjectManagerListener(
109 new ProjectManagerListener() {
111 public void projectOpened(final Project project) {
112 MessageBus messageBus = project.getMessageBus();
113 MessageBusConnection connection = messageBus.connect(project);
114 connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() {
115 public void storageFileChanged(final VirtualFileEvent event, @NotNull final StateStorage storage) {
116 VirtualFile file = event.getFile();
117 if (!file.isDirectory() && !((ApplicationImpl)ApplicationManager.getApplication()).isSaving() && !(event.getRequestor() instanceof StateStorage.SaveSession)) {
118 saveChangedProjectFile(file, project, storage);
124 ProjectManagerListener[] listeners = getListeners(project);
125 for (ProjectManagerListener listener : listeners) {
126 listener.projectOpened(project);
130 public void projectClosed(Project project) {
131 ProjectManagerListener[] listeners = getListeners(project);
132 for (ProjectManagerListener listener : listeners) {
133 listener.projectClosed(project);
137 public boolean canCloseProject(Project project) {
138 ProjectManagerListener[] listeners = getListeners(project);
139 for (ProjectManagerListener listener : listeners) {
140 if (!listener.canCloseProject(project)) {
141 return false;
144 return true;
147 public void projectClosing(Project project) {
148 ProjectManagerListener[] listeners = getListeners(project);
149 for (ProjectManagerListener listener : listeners) {
150 listener.projectClosing(project);
156 registerExternalProjectFileListener(virtualFileManagerEx);
159 public void disposeComponent() {
160 Disposer.dispose(myChangedFilesAlarm);
161 if (myDefaultProject != null) {
162 Disposer.dispose(myDefaultProject);
163 myDefaultProject = null;
167 public void initComponent() {
170 @Nullable
171 public Project newProject(final String projectName, String filePath, boolean useDefaultProjectSettings, boolean isDummy) {
172 filePath = canonicalize(filePath);
174 if (LOG_PROJECT_LEAKAGE_IN_TESTS && ApplicationManager.getApplication().isUnitTestMode()) {
175 for (int i = 0; i < 42; i++) {
176 if (myProjects.size() < MAX_LEAKY_PROJECTS) break;
177 System.gc();
178 try {
179 Thread.sleep(100);
181 catch (InterruptedException e) {
182 throw new RuntimeException(e);
185 System.gc();
188 if (myProjects.size() >= MAX_LEAKY_PROJECTS) {
189 List<Project> copy = new ArrayList<Project>(myProjects.keySet());
190 myProjects.clear();
191 throw new TooManyProjectLeakedException(copy);
195 try {
196 ProjectImpl project =
197 createAndInitProject(projectName, filePath, false, isDummy, ApplicationManager.getApplication().isUnitTestMode(),
198 useDefaultProjectSettings ? getDefaultProject() : null, null);
199 if (LOG_PROJECT_LEAKAGE_IN_TESTS) {
200 myProjects.put(project, null);
202 return project;
204 catch (final Exception e) {
205 LOG.info(e);
206 Messages.showErrorDialog(message(e), ProjectBundle.message("project.load.default.error"));
208 return null;
211 @NonNls
212 private static String message(Throwable e) {
213 String message = e.getMessage();
214 if (message != null) return message;
215 message = e.getLocalizedMessage();
216 if (message != null) return message;
217 message = e.toString();
218 Throwable cause = e.getCause();
219 if (cause != null) {
220 String causeMessage = message(cause);
221 return message + " (cause: " + causeMessage + ")";
224 return message;
227 private ProjectImpl createAndInitProject(String projectName, String filePath, boolean isDefault, boolean isDummy, boolean isOptimiseTestLoadSpeed,
228 @Nullable Project template, @Nullable Pair<Class, Object> additionalPicoContainerComponents) throws IOException {
229 if (isDummy) {
230 throw new UnsupportedOperationException("Dummy project is deprecated and shall not be used anymore.");
232 final ProjectImpl project = isDefault ? new DefaultProject(this, filePath, isOptimiseTestLoadSpeed, projectName) : new ProjectImpl(this, filePath, false, isOptimiseTestLoadSpeed, projectName);
234 if (additionalPicoContainerComponents != null) {
235 project.getPicoContainer().registerComponentInstance(additionalPicoContainerComponents.first, additionalPicoContainerComponents.second);
238 ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).beforeProjectLoaded(project);
240 try {
241 if (template != null) {
242 project.getStateStore().loadProjectFromTemplate((ProjectImpl)template);
243 } else {
244 project.getStateStore().load();
247 catch (IOException e) {
248 scheduleDispose(project);
249 throw e;
251 catch (final StateStorage.StateStorageException e) {
252 scheduleDispose(project);
253 throw e;
256 project.loadProjectComponents();
257 project.init();
258 if (projectName != null) {
259 ProjectDetailsComponent.getInstance(project).setProjectName(projectName);
262 return project;
265 private static void scheduleDispose(final ProjectImpl project) {
266 ApplicationManager.getApplication().invokeLater(new Runnable() {
267 public void run() {
268 Disposer.dispose(project);
273 @Nullable
274 public Project loadProject(String filePath) throws IOException, JDOMException, InvalidDataException {
275 try {
276 return loadProject(filePath, null);
278 catch (StateStorage.StateStorageException e) {
279 throw new IOException(e.getMessage());
283 @Nullable
284 private Project loadProject(String filePath, Pair<Class, Object> additionalPicoContainerComponents) throws IOException, StateStorage.StateStorageException {
285 filePath = canonicalize(filePath);
286 ProjectImpl project = null;
287 try {
288 project = createAndInitProject(null, filePath, false, false, false, null, additionalPicoContainerComponents);
290 catch (ProcessCanceledException e) {
291 if (project != null) {
292 scheduleDispose(project);
294 throw e;
297 return project;
300 @Nullable
301 protected static String canonicalize(final String filePath) {
302 if (filePath == null) return null;
303 try {
304 return FileUtil.resolveShortWindowsName(filePath);
306 catch (IOException e) {
307 // OK. File does not yet exist so it's canonical path will be equal to its original path.
310 return filePath;
313 @TestOnly
314 public synchronized boolean isDefaultProjectInitialized() {
315 return myDefaultProject != null;
317 @NotNull
318 public synchronized Project getDefaultProject() {
319 if (myDefaultProject == null) {
320 try {
321 myDefaultProject = createAndInitProject(null, null, true, false, ApplicationManager.getApplication().isUnitTestMode(), null, null);
322 myDefaultProjectRootElement = null;
324 catch (IOException e) {
325 LOG.error(e);
327 catch (StateStorage.StateStorageException e) {
328 LOG.error(e);
331 return myDefaultProject;
335 public Element getDefaultProjectRootElement() {
336 return myDefaultProjectRootElement;
339 @NotNull
340 public Project[] getOpenProjects() {
341 if (ApplicationManager.getApplication().isUnitTestMode()) {
342 final Project currentTestProject = myCurrentTestProject;
343 if (myOpenProjects.isEmpty() && currentTestProject != null && !currentTestProject.isDisposed()) {
344 return new Project[] {currentTestProject};
347 return myOpenProjects.toArray(new Project[myOpenProjects.size()]);
350 public boolean isProjectOpened(Project project) {
351 if (ApplicationManager.getApplication().isUnitTestMode() && myOpenProjects.isEmpty() && myCurrentTestProject != null) {
352 return project == myCurrentTestProject;
354 return myOpenProjects.contains(project);
357 public boolean openProject(final Project project) {
358 if (myOpenProjects.contains(project)) return false;
359 if (!ApplicationManager.getApplication().isUnitTestMode() && !((ProjectEx)project).getStateStore().checkVersion()) return false;
362 myOpenProjects.add(project);
363 fireProjectOpened(project);
365 final StartupManagerImpl startupManager = (StartupManagerImpl)StartupManager.getInstance(project);
367 boolean ok = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
368 public void run() {
369 startupManager.runStartupActivities();
371 }, ProjectBundle.message("project.load.progress"), true, project);
373 if (!ok) {
374 closeProject(project, false);
375 notifyProjectOpenFailed();
376 return false;
379 startupManager.runPostStartupActivities();
381 return true;
384 public Project loadAndOpenProject(String filePath) throws IOException, JDOMException, InvalidDataException {
385 return loadAndOpenProject(filePath, true);
388 @Nullable
389 public Project loadAndOpenProject(final String filePath, final boolean convert) throws IOException, JDOMException, InvalidDataException {
390 try {
392 final Pair<Class, Object> convertorComponent;
393 if (convert) {
394 try {
395 convertorComponent = convertProject(filePath);
397 catch (ProcessCanceledException e) {
398 return null;
401 else {
402 convertorComponent = null;
405 Project project = loadProjectWithProgress(filePath, convertorComponent);
406 if (project == null) return null;
408 if (!openProject(project)) {
409 Disposer.dispose(project);
410 return null;
413 return project;
415 catch (StateStorage.StateStorageException e) {
416 throw new IOException(e.getMessage());
420 @Nullable
421 public Project loadProjectWithProgress(final String filePath, final Pair<Class, Object> convertorComponent) throws IOException {
422 return loadProjectWithProgress(filePath, convertorComponent, null);
425 @Nullable
426 public Project loadProjectWithProgress(final String filePath, final Pair<Class, Object> convertorComponent, Ref<Boolean> canceled) throws IOException {
427 final IOException[] io = {null};
428 final StateStorage.StateStorageException[] stateStorage = {null};
430 if (filePath != null) {
431 refreshProjectFiles(filePath);
434 final Project[] project = new Project[1];
435 boolean ok = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
436 public void run() {
437 final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
438 try {
439 if (indicator != null) {
440 indicator.setText(ProjectBundle.message("loading.components.for", filePath));
441 indicator.setIndeterminate(true);
443 project[0] = loadProject(filePath, convertorComponent);
445 catch (IOException e) {
446 io[0] = e;
447 return;
449 catch (StateStorage.StateStorageException e) {
450 stateStorage[0] = e;
451 return;
454 if (indicator != null) {
455 indicator.setText(ProjectBundle.message("initializing.components"));
458 }, ProjectBundle.message("project.load.progress"), true, null);
460 if (!ok) {
461 if (project[0] != null) {
462 Disposer.dispose(project[0]);
463 project[0] = null;
465 if (canceled != null) {
466 canceled.set(true);
468 notifyProjectOpenFailed();
471 if (io[0] != null) throw io[0];
472 if (stateStorage[0] != null) throw stateStorage[0];
474 if (project[0] == null || !ok) {
475 return null;
477 return project [0];
480 private static void refreshProjectFiles(final String filePath) {
481 if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isDispatchThread()) {
482 final File file = new File(filePath);
483 if (file.isFile()) {
484 VirtualFile projectFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePath);
485 if (projectFile != null) {
486 projectFile.refresh(false, false);
489 File iwsFile = new File(file.getParentFile(), FileUtil.getNameWithoutExtension(file) + WorkspaceFileType.DOT_DEFAULT_EXTENSION);
490 VirtualFile wsFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(iwsFile);
491 if (wsFile != null) {
492 wsFile.refresh(false, false);
496 else {
497 VirtualFile projectConfigDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(filePath, Project.DIRECTORY_STORE_FOLDER));
498 if (projectConfigDir != null && projectConfigDir.isDirectory()) {
499 projectConfigDir.getChildren();
500 if (projectConfigDir instanceof NewVirtualFile) {
501 ((NewVirtualFile)projectConfigDir).markDirtyRecursively();
503 projectConfigDir.refresh(false, true);
510 @Nullable
511 protected Pair<Class, Object> convertProject(final String filePath) throws ProcessCanceledException {
512 return null;
515 private static void notifyProjectOpenFailed() {
516 ApplicationManager.getApplication().getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).projectOpenFailed();
519 private void registerExternalProjectFileListener(VirtualFileManagerEx virtualFileManager) {
520 virtualFileManager.addVirtualFileManagerListener(new VirtualFileManagerListener() {
521 public void beforeRefreshStart(boolean asynchonous) {
524 public void afterRefreshFinish(boolean asynchonous) {
525 scheduleReloadApplicationAndProject();
530 private void askToReloadProjectIfConfigFilesChangedExternally() {
531 if (!myChangedProjectFiles.isEmpty() && myReloadBlockCount == 0) {
532 Set<Project> projects = myChangedProjectFiles.keySet();
533 List<Project> projectsToReload = new ArrayList<Project>();
535 for (Project project : projects) {
536 if (shouldReloadProject(project)) {
537 projectsToReload.add(project);
541 for (final Project projectToReload : projectsToReload) {
542 reloadProjectImpl(projectToReload, false, false);
545 myChangedProjectFiles.clear();
550 private boolean tryToReloadApplication(){
551 try {
552 final Application app = ApplicationManager.getApplication();
554 if (app.isDisposed()) return false;
555 final HashSet<Pair<VirtualFile, StateStorage>> causes = new HashSet<Pair<VirtualFile, StateStorage>>(myChangedApplicationFiles);
556 if (causes.isEmpty()) return true;
558 final boolean[] reloadOk = {false};
559 final LinkedHashSet<String> components = new LinkedHashSet<String>();
561 ApplicationManager.getApplication().runWriteAction(new Runnable() {
562 public void run() {
563 try {
565 reloadOk[0] = ((ApplicationImpl)app).getStateStore().reload(causes, components);
567 catch (StateStorage.StateStorageException e) {
568 Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
569 ProjectBundle.message("project.reload.failed.title"));
571 catch (IOException e) {
572 Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
573 ProjectBundle.message("project.reload.failed.title"));
578 if (!reloadOk[0] && !components.isEmpty()) {
579 String message = "Application components were changed externally and cannot be reloaded:\n";
580 for (String component : components) {
581 message += component + "\n";
583 message += "Shutdown IDEA?";
585 if (Messages.showYesNoDialog(message,
586 "Application Configuration Reload", Messages.getQuestionIcon()) == 0) {
587 for (Pair<VirtualFile, StateStorage> cause : causes) {
588 StateStorage stateStorage = cause.getSecond();
589 if (stateStorage instanceof XmlElementStorage) {
590 ((XmlElementStorage)stateStorage).disableSaving();
593 ApplicationManagerEx.getApplicationEx().exit(true);
598 return reloadOk[0];
600 finally {
601 myChangedApplicationFiles.clear();
606 private boolean shouldReloadProject(final Project project) {
607 if (project.isDisposed()) return false;
608 final HashSet<Pair<VirtualFile, StateStorage>> causes = new HashSet<Pair<VirtualFile, StateStorage>>(myChangedProjectFiles.get(project));
610 final boolean[] reloadOk = {false};
612 ApplicationManager.getApplication().runWriteAction(new Runnable() {
613 public void run() {
614 try {
615 reloadOk[0] = ((ProjectEx)project).getStateStore().reload(causes);
617 catch (StateStorage.StateStorageException e) {
618 Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
619 ProjectBundle.message("project.reload.failed.title"));
621 catch (IOException e) {
622 Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
623 ProjectBundle.message("project.reload.failed.title"));
627 if (reloadOk[0]) return false;
629 String message;
630 if (causes.size() == 1) {
631 message = ProjectBundle.message("project.reload.external.change.single", causes.iterator().next().first.getPresentableUrl());
633 else {
634 StringBuilder filesBuilder = new StringBuilder();
635 boolean first = true;
636 Set<String> alreadyShown = new HashSet<String>();
637 for (Pair<VirtualFile, StateStorage> cause : causes) {
638 String url = cause.first.getPresentableUrl();
639 if (!alreadyShown.contains(url)) {
640 if (!first) filesBuilder.append("\n");
641 first = false;
642 filesBuilder.append(url);
643 alreadyShown.add(url);
646 message = ProjectBundle.message("project.reload.external.change.multiple", filesBuilder.toString());
649 return Messages.showYesNoDialog(project, message, ProjectBundle.message("project.reload.external.change.title"), Messages.getQuestionIcon()) == 0;
652 public boolean isFileSavedToBeReloaded(VirtualFile candidate) {
653 return mySavedCopies.containsKey(candidate);
656 public void blockReloadingProjectOnExternalChanges() {
657 myReloadBlockCount++;
660 public void unblockReloadingProjectOnExternalChanges() {
661 myReloadBlockCount--;
662 scheduleReloadApplicationAndProject();
665 private void scheduleReloadApplicationAndProject() {
666 ApplicationManager.getApplication().invokeLater(new Runnable() {
667 public void run() {
668 if (!tryToReloadApplication()) return;
669 askToReloadProjectIfConfigFilesChangedExternally();
672 }, ModalityState.NON_MODAL);
676 public void setCurrentTestProject(@Nullable final Project project) {
677 assert ApplicationManager.getApplication().isUnitTestMode();
678 myCurrentTestProject = project;
681 @Nullable
682 public Project getCurrentTestProject() {
683 assert ApplicationManager.getApplication().isUnitTestMode();
684 return myCurrentTestProject;
687 public void saveChangedProjectFile(final VirtualFile file, final Project project) {
688 if (file.exists()) {
689 copyToTemp(file);
691 registerProjectToReload(project, file, null);
694 private void saveChangedProjectFile(final VirtualFile file, final Project project, final StateStorage storage) {
695 if (file.exists()) {
696 copyToTemp(file);
698 registerProjectToReload(project, file, storage);
702 private void registerProjectToReload(final Project project, final VirtualFile cause, final StateStorage storage) {
703 if (project != null) {
704 List<Pair<VirtualFile, StateStorage>> changedProjectFiles = myChangedProjectFiles.get(project);
706 if (changedProjectFiles == null) {
707 changedProjectFiles = new ArrayList<Pair<VirtualFile, StateStorage>>();
708 myChangedProjectFiles.put(project, changedProjectFiles);
711 changedProjectFiles.add(new Pair<VirtualFile, StateStorage>(cause, storage));
713 else {
714 myChangedApplicationFiles.add(new Pair<VirtualFile, StateStorage>(cause, storage));
717 myChangedFilesAlarm.cancelAllRequests();
718 myChangedFilesAlarm.addRequest(new Runnable() {
719 public void run() {
720 if (myReloadBlockCount == 0) {
721 scheduleReloadApplicationAndProject();
724 }, 444);
727 private void copyToTemp(VirtualFile file) {
728 try {
729 final byte[] bytes = file.contentsToByteArray();
730 mySavedCopies.put(file, bytes);
731 mySavedTimestamps.put(file, file.getTimeStamp());
733 catch (IOException e) {
734 LOG.error(e);
738 private void restoreCopy(VirtualFile file) {
739 try {
740 if (file == null) return; // Externally deleted actually.
741 if (!file.isWritable()) return; // IDEA was unable to save it as well. So no need to restore.
743 final byte[] bytes = mySavedCopies.get(file);
744 if (bytes != null) {
745 try {
746 file.setBinaryContent(bytes, -1, mySavedTimestamps.get(file));
748 catch (IOException e) {
749 Messages.showWarningDialog(ProjectBundle.message("project.reload.write.failed", file.getPresentableUrl()),
750 ProjectBundle.message("project.reload.write.failed.title"));
754 finally {
755 mySavedCopies.remove(file);
756 mySavedTimestamps.remove(file);
760 public void reloadProject(final Project p) {
761 reloadProjectImpl(p, true, false);
764 public void reloadProjectImpl(final Project p, final boolean clearCopyToRestore, boolean takeMemorySnapshot) {
765 if (clearCopyToRestore) {
766 mySavedCopies.clear();
767 mySavedTimestamps.clear();
769 reloadProject(p, takeMemorySnapshot);
772 public void reloadProject(@NotNull Project p, final boolean takeMemorySnapshot) {
773 final Project[] project = {p};
775 ProjectReloadState.getInstance(project[0]).onBeforeAutomaticProjectReload();
776 final Application application = ApplicationManager.getApplication();
778 application.invokeLater(new Runnable() {
779 public void run() {
780 LOG.info("Reloading project.");
781 ProjectImpl projectImpl = (ProjectImpl)project[0];
782 if (projectImpl.isDisposed()) return;
783 IProjectStore projectStore = projectImpl.getStateStore();
784 final String location = projectImpl.getLocation();
786 final List<IFile> original;
787 try {
788 final IComponentStore.SaveSession saveSession = projectStore.startSave();
789 original = saveSession.getAllStorageFiles(true);
790 saveSession.finishSave();
792 catch (IOException e) {
793 LOG.error(e);
794 return;
797 if (project[0].isDisposed() || ProjectUtil.closeProject(project[0])) {
798 application.runWriteAction(new Runnable() {
799 public void run() {
800 for (final IFile originalFile : original) {
801 restoreCopy(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(originalFile));
806 project[0] = null; // Let it go.
808 if (takeMemorySnapshot) {
809 ProfilingUtil.forceCaptureMemorySnapshot();
812 ProjectUtil.openProject(location, null, true);
815 }, ModalityState.NON_MODAL);
819 public boolean isOpeningProject() {
820 return myCountOfProjectsBeingOpen > 0;
824 public boolean closeProject(final Project project) {
825 return closeProject(project, true);
828 private boolean closeProject(final Project project, final boolean save) {
829 if (!isProjectOpened(project)) return true;
830 if (!canClose(project)) return false;
832 final ShutDownTracker shutDownTracker = ShutDownTracker.getInstance();
833 shutDownTracker.registerStopperThread(Thread.currentThread());
834 try {
835 if (save) {
836 FileDocumentManager.getInstance().saveAllDocuments();
837 project.save();
839 fireProjectClosing(project);
841 myOpenProjects.remove(project);
842 fireProjectClosed(project);
844 if (save) {
845 ApplicationEx application = ApplicationManagerEx.getApplicationEx();
846 if (!application.isUnitTestMode()) application.saveSettings();
849 finally {
850 shutDownTracker.unregisterStopperThread(Thread.currentThread());
853 return true;
856 private void fireProjectClosing(Project project) {
857 if (LOG.isDebugEnabled()) {
858 LOG.debug("enter: fireProjectClosing()");
861 for (ProjectManagerListener listener : myListeners) {
862 try {
863 listener.projectClosing(project);
865 catch (Exception e) {
866 LOG.error(e);
871 public void addProjectManagerListener(ProjectManagerListener listener) {
872 myListeners.add(listener);
875 public void removeProjectManagerListener(ProjectManagerListener listener) {
876 boolean removed = myListeners.remove(listener);
877 LOG.assertTrue(removed);
880 public void addProjectManagerListener(Project project, ProjectManagerListener listener) {
881 ArrayList<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY);
882 if (listeners == null) {
883 listeners = new ArrayList<ProjectManagerListener>();
884 project.putUserData(LISTENERS_IN_PROJECT_KEY, listeners);
886 listeners.add(listener);
889 public void removeProjectManagerListener(Project project, ProjectManagerListener listener) {
890 ArrayList<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY);
891 if (listeners != null) {
892 boolean removed = listeners.remove(listener);
893 LOG.assertTrue(removed);
895 else {
896 LOG.assertTrue(false);
900 private void fireProjectOpened(Project project) {
901 if (LOG.isDebugEnabled()) {
902 LOG.debug("projectOpened");
905 for (ProjectManagerListener listener : myListeners) {
906 try {
907 listener.projectOpened(project);
909 catch (Exception e) {
910 LOG.error(e);
915 private void fireProjectClosed(Project project) {
916 if (LOG.isDebugEnabled()) {
917 LOG.debug("projectClosed");
920 for (ProjectManagerListener listener : myListeners) {
921 try {
922 listener.projectClosed(project);
924 catch (Exception e) {
925 LOG.error(e);
930 public boolean canClose(Project project) {
931 if (LOG.isDebugEnabled()) {
932 LOG.debug("enter: canClose()");
935 for (ProjectManagerListener listener : myListeners) {
936 try {
937 if (!listener.canCloseProject(project)) return false;
938 } catch (Throwable e) {
939 LOG.warn(e); // DO NOT LET ANY PLUGIN to prevent closing due to exception
943 return true;
946 public void writeExternal(Element parentNode) throws WriteExternalException {
947 if (myDefaultProject != null) {
948 myDefaultProject.save();
951 if (myDefaultProjectRootElement == null) { //read external isn't called if config folder is absent
952 myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT);
955 myDefaultProjectRootElement.detach();
956 parentNode.addContent(myDefaultProjectRootElement);
960 public void setDefaultProjectRootElement(final Element defaultProjectRootElement) {
961 myDefaultProjectRootElement = defaultProjectRootElement;
964 public void readExternal(Element parentNode) throws InvalidDataException {
965 myDefaultProjectRootElement = parentNode.getChild(ELEMENT_DEFAULT_PROJECT);
967 if (myDefaultProjectRootElement == null) {
968 myDefaultProjectRootElement = new Element(ELEMENT_DEFAULT_PROJECT);
971 myDefaultProjectRootElement.detach();
974 public String getExternalFileName() {
975 return "project.default";
978 @NotNull
979 public String getComponentName() {
980 return "ProjectManager";
983 @NotNull
984 public File[] getExportFiles() {
985 return new File[]{PathManager.getOptionsFile(this)};
988 @NotNull
989 public String getPresentableName() {
990 return ProjectBundle.message("project.default.settings");