/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.project.impl;
import com.intellij.conversion.ConversionResult;
import com.intellij.conversion.ConversionService;
import com.intellij.ide.AppLifecycleListener;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.startup.StartupManagerEx;
import com.intellij.ide.startup.impl.StartupManagerImpl;
import com.intellij.notification.NotificationsManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.components.*;
import com.intellij.openapi.components.impl.stores.ComponentStoreImpl;
import com.intellij.openapi.components.impl.stores.ComponentStoreImpl.ReloadComponentStoreStatus;
import com.intellij.openapi.components.impl.stores.FileBasedStorage;
import com.intellij.openapi.components.impl.stores.StateStorageManager;
import com.intellij.openapi.components.impl.stores.StorageUtil;
import com.intellij.openapi.components.store.StateStorageBase;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.project.*;
import com.intellij.openapi.project.ex.ProjectEx;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.ex.VirtualFileManagerAdapter;
import com.intellij.openapi.vfs.impl.ZipHandler;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame;
import com.intellij.ui.GuiUtils;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.ui.UIUtil;
import consulo.annotations.RequiredDispatchThread;
import gnu.trove.THashSet;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@State(name = "ProjectManager", storages = {@Storage(file = StoragePathMacros.APP_CONFIG + "/project.default.xml")})
public class ProjectManagerImpl extends ProjectManagerEx implements PersistentStateComponent<Element>, Disposable {
private static final Logger LOG = Logger.getInstance(ProjectManagerImpl.class);
private static final Key<List<ProjectManagerListener>> LISTENERS_IN_PROJECT_KEY = Key.create("LISTENERS_IN_PROJECT_KEY");
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
private ProjectImpl myDefaultProject; // Only used asynchronously in save and dispose, which itself are synchronized.
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
private Element myDefaultProjectRootElement; // Only used asynchronously in save and dispose, which itself are synchronized.
private Project[] myOpenProjects = {}; // guarded by lock
private final Object lock = new Object();
private final List<ProjectManagerListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final MultiMap<Project, StateStorage> myChangedProjectFiles = MultiMap.createSet();
private final SingleAlarm myChangedFilesAlarm;
private final List<StateStorage> myChangedApplicationFiles = new SmartList<>();
private final AtomicInteger myReloadBlockCount = new AtomicInteger(0);
private final ProgressManager myProgressManager;
private volatile boolean myDefaultProjectWasDisposed = false;
private final Runnable restartApplicationOrReloadProjectTask = new Runnable() {
@Override
public void run() {
if (isReloadUnblocked() && tryToReloadApplication()) {
askToReloadProjectIfConfigFilesChangedExternally();
}
}
};
@NotNull
private static List<ProjectManagerListener> getListeners(Project project) {
List<ProjectManagerListener> array = project.getUserData(LISTENERS_IN_PROJECT_KEY);
if (array == null) return Collections.emptyList();
return array;
}
/**
* @noinspection UnusedParameters
*/
public ProjectManagerImpl(@NotNull VirtualFileManager virtualFileManager, ProgressManager progressManager) {
myProgressManager = progressManager;
Application app = ApplicationManager.getApplication();
MessageBus messageBus = app.getMessageBus();
messageBus.connect(app).subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() {
@Override
public void storageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage) {
projectStorageFileChanged(event, storage, null);
}
});
final ProjectManagerListener busPublisher = messageBus.syncPublisher(TOPIC);
addProjectManagerListener(new ProjectManagerListener() {
@Override
public void projectOpened(final Project project) {
project.getMessageBus().connect(project).subscribe(StateStorage.PROJECT_STORAGE_TOPIC, new StateStorage.Listener() {
@Override
public void storageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage) {
projectStorageFileChanged(event, storage, project);
}
});
busPublisher.projectOpened(project);
for (ProjectManagerListener listener : getListeners(project)) {
listener.projectOpened(project);
}
}
@Override
public void projectClosed(Project project) {
busPublisher.projectClosed(project);
for (ProjectManagerListener listener : getListeners(project)) {
listener.projectClosed(project);
}
ZipHandler.clearFileAccessorCache();
LaterInvocator.purgeExpiredItems();
}
@Override
public boolean canCloseProject(Project project) {
for (ProjectManagerListener listener : getListeners(project)) {
if (!listener.canCloseProject(project)) {
return false;
}
}
return true;
}
@Override
public void projectClosing(Project project) {
busPublisher.projectClosing(project);
for (ProjectManagerListener listener : getListeners(project)) {
listener.projectClosing(project);
}
}
});
virtualFileManager.addVirtualFileManagerListener(new VirtualFileManagerAdapter() {
@Override
public void beforeRefreshStart(boolean asynchronous) {
blockReloadingProjectOnExternalChanges();
}
@Override
public void afterRefreshFinish(boolean asynchronous) {
unblockReloadingProjectOnExternalChanges();
}
});
myChangedFilesAlarm = new SingleAlarm(restartApplicationOrReloadProjectTask, 300);
}
private void projectStorageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage, @Nullable Project project) {
VirtualFile file = event.getFile();
if (!StorageUtil.isChangedByStorageOrSaveSession(event) && !(event.getRequestor() instanceof ProjectManagerImpl)) {
registerProjectToReload(project, file, storage);
}
}
@Override
public void dispose() {
ApplicationManager.getApplication().assertWriteAccessAllowed();
Disposer.dispose(myChangedFilesAlarm);
if (myDefaultProject != null) {
Disposer.dispose(myDefaultProject);
myDefaultProject = null;
myDefaultProjectWasDisposed = true;
}
}
private static final boolean LOG_PROJECT_LEAKAGE_IN_TESTS = false;
private static final int MAX_LEAKY_PROJECTS = 42;
@SuppressWarnings("FieldCanBeLocal")
private final Map<Project, String> myProjects = new WeakHashMap<Project, String>();
@Override
@Nullable
public Project newProject(final String projectName, @NotNull String dirPath, boolean useDefaultProjectSettings, boolean isDummy) {
dirPath = toCanonicalName(dirPath);
//noinspection ConstantConditions
if (LOG_PROJECT_LEAKAGE_IN_TESTS && ApplicationManager.getApplication().isUnitTestMode()) {
for (int i = 0; i < 42; i++) {
if (myProjects.size() < MAX_LEAKY_PROJECTS) break;
System.gc();
TimeoutUtil.sleep(100);
System.gc();
}
if (myProjects.size() >= MAX_LEAKY_PROJECTS) {
List<Project> copy = new ArrayList<Project>(myProjects.keySet());
myProjects.clear();
throw new TooManyProjectLeakedException(copy);
}
}
ProjectImpl project = createProject(projectName, dirPath, false, ApplicationManager.getApplication().isUnitTestMode());
try {
initProject(project, useDefaultProjectSettings ? (ProjectImpl)getDefaultProject() : null);
if (LOG_PROJECT_LEAKAGE_IN_TESTS) {
myProjects.put(project, null);
}
return project;
}
catch (Throwable t) {
LOG.info(t);
Messages.showErrorDialog(message(t), ProjectBundle.message("project.load.default.error"));
return null;
}
}
@NonNls
private static String message(Throwable e) {
String message = e.getMessage();
if (message != null) return message;
message = e.getLocalizedMessage();
//noinspection ConstantConditions
if (message != null) return message;
message = e.toString();
Throwable cause = e.getCause();
if (cause != null) {
String causeMessage = message(cause);
return message + " (cause: " + causeMessage + ")";
}
return message;
}
private void initProject(@NotNull final ProjectImpl project, @Nullable ProjectImpl template) throws IOException {
ProgressIndicator indicator = myProgressManager.getProgressIndicator();
if (indicator != null && !project.isDefault()) {
indicator.setText(ProjectBundle.message("loading.components.for", project.getName()));
indicator.setIndeterminate(true);
}
ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).beforeProjectLoaded(project);
boolean succeed = false;
try {
if (template != null) {
project.getStateStore().loadProjectFromTemplate(template);
}
else {
project.getStateStore().load();
}
project.loadProjectComponents();
project.init();
succeed = true;
}
finally {
if (!succeed && !project.isDefault()) {
TransactionGuard.submitTransaction(project, new Runnable() {
@Override
public void run() {
WriteAction.run(new ThrowableRunnable<RuntimeException>() {
@Override
public void run() throws RuntimeException {
Disposer.dispose(project);
}
});
}
});
}
}
}
private ProjectImpl createProject(@Nullable String projectName, @NotNull String dirPath, boolean isDefault, boolean isOptimiseTestLoadSpeed) {
return isDefault
? new DefaultProject(this, "", isOptimiseTestLoadSpeed)
: new ProjectImpl(this, new File(dirPath).getAbsolutePath(), isOptimiseTestLoadSpeed, projectName);
}
private static void scheduleDispose(final ProjectImpl project) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
if (!project.isDisposed()) {
Disposer.dispose(project);
}
}
});
}
});
}
@Override
@Nullable
public Project loadProject(@NotNull String filePath) throws IOException, JDOMException, InvalidDataException {
try {
ProjectImpl project = createProject(null, filePath, false, false);
initProject(project, null);
return project;
}
catch (Throwable t) {
LOG.info(t);
throw new IOException(t);
}
}
@NotNull
private static String toCanonicalName(@NotNull final String filePath) {
try {
return FileUtil.resolveShortWindowsName(filePath);
}
catch (IOException e) {
// OK. File does not yet exist so it's canonical path will be equal to its original path.
}
return filePath;
}
@TestOnly
public synchronized boolean isDefaultProjectInitialized() {
return myDefaultProject != null;
}
@Override
@NotNull
public synchronized Project getDefaultProject() {
LOG.assertTrue(!myDefaultProjectWasDisposed, "Default project has been already disposed!");
if (myDefaultProject == null) {
ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
@Override
public void run() {
try {
myDefaultProject = createProject(null, "", true, ApplicationManager.getApplication().isUnitTestMode());
initProject(myDefaultProject, null);
myDefaultProjectRootElement = null;
}
catch (Throwable t) {
PluginManager.processException(t);
}
}
});
}
return myDefaultProject;
}
@Nullable
public Element getDefaultProjectRootElement() {
return myDefaultProjectRootElement;
}
@Override
@NotNull
public Project[] getOpenProjects() {
synchronized (lock) {
return myOpenProjects;
}
}
@Override
public boolean isProjectOpened(Project project) {
synchronized (lock) {
return ArrayUtil.contains(project, myOpenProjects);
}
}
@Override
public boolean openProject(final Project project) {
if (isLight(project)) {
((ProjectImpl)project).setTemporarilyDisposed(false);
boolean isInitialized = StartupManagerEx.getInstanceEx(project).startupActivityPassed();
if (isInitialized) {
addToOpened(project);
// events already fired
return true;
}
}
for (Project p : getOpenProjects()) {
if (ProjectUtil.isSameProject(project.getProjectFilePath(), p)) {
GuiUtils.invokeLaterIfNeeded(() -> ProjectUtil.focusProjectWindow(p, false), ModalityState.NON_MODAL);
return false;
}
}
if (!addToOpened(project)) {
return false;
}
Runnable process = () -> {
TransactionGuard.getInstance().submitTransactionAndWait(() -> fireProjectOpened(project));
final StartupManagerImpl startupManager = (StartupManagerImpl)StartupManager.getInstance(project);
startupManager.runStartupActivities();
// Startup activities (e.g. the one in FileBasedIndexProjectHandler) have scheduled dumb mode to begin "later"
// Now we schedule-and-wait to the same event queue to guarantee that the dumb mode really begins now:
// Post-startup activities should not ever see unindexed and at the same time non-dumb state
TransactionGuard.getInstance().submitTransactionAndWait(startupManager::startCacheUpdate);
startupManager.runPostStartupActivitiesFromExtensions();
GuiUtils.invokeLaterIfNeeded(() -> {
if (!project.isDisposed()) {
startupManager.runPostStartupActivities();
Application application = ApplicationManager.getApplication();
if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
final TrackingPathMacroSubstitutor macroSubstitutor = ((ProjectEx)project).getStateStore().getStateStorageManager().getMacroSubstitutor();
if (macroSubstitutor != null) {
StorageUtil.notifyUnknownMacros(macroSubstitutor, project, null);
}
}
if (ApplicationManager.getApplication().isActive()) {
JFrame projectFrame = WindowManager.getInstance().getFrame(project);
if (projectFrame != null) {
IdeFocusManager.getInstance(project).requestFocus(projectFrame, true);
}
}
}
}, ModalityState.NON_MODAL);
};
ProgressIndicator indicator = myProgressManager.getProgressIndicator();
if (indicator != null) {
indicator.setText("Preparing workspace...");
process.run();
return true;
}
boolean ok =
myProgressManager.runProcessWithProgressSynchronously(process, ProjectBundle.message("project.load.progress"), canCancelProjectLoading(), project);
if (!ok) {
closeProject(project, false, false, true);
notifyProjectOpenFailed();
return false;
}
return true;
}
private boolean addToOpened(@NotNull Project project) {
assert !project.isDisposed() : "Must not open already disposed project";
synchronized (lock) {
if (isProjectOpened(project)) {
return false;
}
myOpenProjects = ArrayUtil.append(myOpenProjects, project);
ProjectCoreUtil.theProject = myOpenProjects.length == 1 ? project : null;
}
return true;
}
@NotNull
private Collection<Project> removeFromOpened(@NotNull Project project) {
synchronized (lock) {
myOpenProjects = ArrayUtil.remove(myOpenProjects, project);
ProjectCoreUtil.theProject = myOpenProjects.length == 1 ? myOpenProjects[0] : null;
return Arrays.asList(myOpenProjects);
}
}
private static boolean canCancelProjectLoading() {
ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
return !(indicator instanceof NonCancelableSection);
}
@Override
public Project loadAndOpenProject(@NotNull final String filePath) throws IOException {
final Project project = convertAndLoadProject(filePath);
if (project == null) {
WelcomeFrame.showIfNoProjectOpened();
return null;
}
// todo unify this logic with PlatformProjectOpenProcessor
if (!openProject(project)) {
WelcomeFrame.showIfNoProjectOpened();
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
Disposer.dispose(project);
}
});
}
return project;
}
/**
* Converts and loads the project at the specified path.
*
* @param filePath the path to open the project.
* @return the project, or null if the user has cancelled opening the project.
*/
@Override
@Nullable
public Project convertAndLoadProject(String filePath) throws IOException {
final String fp = toCanonicalName(filePath);
final ConversionResult conversionResult = ConversionService.getInstance().convert(fp);
if (conversionResult.openingIsCanceled()) {
return null;
}
final Project project;
try {
project = loadProjectWithProgress(filePath);
if (project == null) return null;
}
catch (IOException e) {
LOG.info(e);
throw e;
}
catch (Throwable t) {
LOG.info(t);
throw new IOException(t);
}
if (!conversionResult.conversionNotNeeded()) {
StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() {
@Override
public void run() {
conversionResult.postStartupActivity(project);
}
});
}
return project;
}
/**
* Opens the project at the specified path.
*
* @param filePath the path to open the project.
* @return the project, or null if the user has cancelled opening the project.
*/
@Nullable
private Project loadProjectWithProgress(@NotNull final String filePath) throws IOException {
final ProjectImpl project = createProject(null, toCanonicalName(filePath), false, false);
try {
myProgressManager.runProcessWithProgressSynchronously(new ThrowableComputable<Project, IOException>() {
@Override
@Nullable
public Project compute() throws IOException {
initProject(project, null);
return project;
}
}, ProjectBundle.message("project.load.progress"), canCancelProjectLoading(), project);
}
catch (StateStorageException e) {
throw new IOException(e);
}
catch (ProcessCanceledException ignore) {
return null;
}
return project;
}
private static void notifyProjectOpenFailed() {
ApplicationManager.getApplication().getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).projectOpenFailed();
WelcomeFrame.showIfNoProjectOpened();
}
private void askToReloadProjectIfConfigFilesChangedExternally() {
Set<Project> projects;
synchronized (myChangedProjectFiles) {
if (myChangedProjectFiles.isEmpty()) {
return;
}
projects = new THashSet<Project>(myChangedProjectFiles.keySet());
}
List<Project> projectsToReload = new SmartList<Project>();
for (Project project : projects) {
if (shouldReloadProject(project)) {
projectsToReload.add(project);
}
}
for (Project project : projectsToReload) {
doReloadProject(project);
}
}
private boolean tryToReloadApplication() {
if (ApplicationManager.getApplication().isDisposed()) {
return false;
}
if (myChangedApplicationFiles.isEmpty()) {
return true;
}
Set<StateStorage> causes = new THashSet<>(myChangedApplicationFiles);
myChangedApplicationFiles.clear();
ReloadComponentStoreStatus status = ComponentStoreImpl.reloadStore(causes, ((ApplicationImpl)ApplicationManager.getApplication()).getStateStore());
if (status == ReloadComponentStoreStatus.RESTART_AGREED) {
ApplicationManagerEx.getApplicationEx().restart(true);
return false;
}
else {
return status == ReloadComponentStoreStatus.SUCCESS || status == ReloadComponentStoreStatus.RESTART_CANCELLED;
}
}
private boolean shouldReloadProject(@NotNull Project project) {
if (project.isDisposed()) {
return false;
}
Collection<StateStorage> causes = new SmartList<>();
Collection<StateStorage> changes;
synchronized (myChangedProjectFiles) {
changes = myChangedProjectFiles.remove(project);
if (!ContainerUtil.isEmpty(changes)) {
for (StateStorage change : changes) {
causes.add(change);
}
}
}
if (causes.isEmpty()) {
return false;
}
return ComponentStoreImpl.reloadStore(causes, ((ProjectEx)project).getStateStore()) == ReloadComponentStoreStatus.RESTART_AGREED;
}
@Override
public void blockReloadingProjectOnExternalChanges() {
myReloadBlockCount.incrementAndGet();
}
@Override
public void unblockReloadingProjectOnExternalChanges() {
if (myReloadBlockCount.decrementAndGet() == 0 && myChangedFilesAlarm.isEmpty()) {
ApplicationManager.getApplication().invokeLater(restartApplicationOrReloadProjectTask, ModalityState.NON_MODAL);
}
}
private boolean isReloadUnblocked() {
int count = myReloadBlockCount.get();
if (LOG.isDebugEnabled()) {
LOG.debug("[RELOAD] myReloadBlockCount = " + count);
}
return count == 0;
}
@Override
public void openTestProject(@NotNull final Project project) {
assert ApplicationManager.getApplication().isUnitTestMode();
openProject(project);
UIUtil.dispatchAllInvocationEvents(); // post init activities are invokeLatered
}
@Override
public Collection<Project> closeTestProject(@NotNull Project project) {
assert ApplicationManager.getApplication().isUnitTestMode();
closeProject(project, false, false, false);
Project[] projects = getOpenProjects();
return projects.length == 0 ? Collections.<Project>emptyList() : Arrays.asList(projects);
}
@Override
public void saveChangedProjectFile(@NotNull VirtualFile file, @NotNull Project project) {
StateStorageManager storageManager = ((ProjectEx)project).getStateStore().getStateStorageManager();
String fileSpec = storageManager.collapseMacros(file.getPath());
Couple<Collection<FileBasedStorage>> storages =
storageManager.getCachedFileStateStorages(Collections.singletonList(fileSpec), Collections.<String>emptyList());
FileBasedStorage storage = ContainerUtil.getFirstItem(storages.first);
// if empty, so, storage is not yet loaded, so, we don't have to reload
if (storage != null) {
registerProjectToReload(project, file, storage);
}
}
private void registerProjectToReload(@Nullable Project project, @NotNull VirtualFile file, @NotNull StateStorage storage) {
if (LOG.isDebugEnabled()) {
LOG.debug("[RELOAD] Registering project to reload: " + file, new Exception());
}
if (project == null) {
myChangedApplicationFiles.add(storage);
}
else {
myChangedProjectFiles.putValue(project, storage);
}
if (storage instanceof StateStorageBase) {
((StateStorageBase)storage).disableSaving();
}
if (isReloadUnblocked()) {
myChangedFilesAlarm.cancelAndRequest();
}
}
@Override
public void reloadProject(@NotNull Project project) {
myChangedProjectFiles.remove(project);
doReloadProject(project);
}
private static void doReloadProject(@NotNull Project project) {
final Ref<Project> projectRef = Ref.create(project);
ProjectReloadState.getInstance(project).onBeforeAutomaticProjectReload();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
LOG.debug("Reloading project.");
Project project = projectRef.get();
// Let it go
projectRef.set(null);
if (project.isDisposed()) {
return;
}
// must compute here, before project dispose
String presentableUrl = project.getPresentableUrl();
if (!ProjectUtil.closeAndDispose(project)) {
return;
}
ProjectUtil.open(presentableUrl, null, true);
}
}, ModalityState.NON_MODAL);
}
@Override
public boolean closeProject(@NotNull final Project project) {
return closeProject(project, true, false, true);
}
@RequiredDispatchThread
public boolean closeProject(@NotNull final Project project, final boolean save, final boolean dispose, boolean checkCanClose) {
if (isLight(project)) {
removeFromOpened(project);
return true;
}
else {
if (!isProjectOpened(project)) return true;
}
if (checkCanClose && !canClose(project)) return false;
final ShutDownTracker shutDownTracker = ShutDownTracker.getInstance();
shutDownTracker.registerStopperThread(Thread.currentThread());
try {
if (save) {
FileDocumentManager.getInstance().saveAllDocuments();
project.save();
}
if (checkCanClose && !ensureCouldCloseIfUnableToSave(project)) {
return false;
}
fireProjectClosing(project); // somebody can start progress here, do not wrap in write action
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
removeFromOpened(project);
fireProjectClosed(project);
if (dispose) {
Disposer.dispose(project);
}
}
});
}
finally {
shutDownTracker.unregisterStopperThread(Thread.currentThread());
}
return true;
}
public static boolean isLight(@NotNull Project project) {
return ApplicationManager.getApplication().isUnitTestMode() && project.toString().contains("light_temp_");
}
@Override
public boolean closeAndDispose(@NotNull final Project project) {
return closeProject(project, true, true, true);
}
private void fireProjectClosing(Project project) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: fireProjectClosing()");
}
for (ProjectManagerListener listener : myListeners) {
try {
listener.projectClosing(project);
}
catch (Exception e) {
LOG.error(e);
}
}
}
@Override
public void addProjectManagerListener(@NotNull ProjectManagerListener listener) {
myListeners.add(listener);
}
@Override
public void addProjectManagerListener(@NotNull final ProjectManagerListener listener, @NotNull Disposable parentDisposable) {
addProjectManagerListener(listener);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
removeProjectManagerListener(listener);
}
});
}
@Override
public void removeProjectManagerListener(@NotNull ProjectManagerListener listener) {
boolean removed = myListeners.remove(listener);
LOG.assertTrue(removed);
}
@Override
public void addProjectManagerListener(@NotNull Project project, @NotNull ProjectManagerListener listener) {
List<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY);
if (listeners == null) {
listeners =
((UserDataHolderEx)project).putUserDataIfAbsent(LISTENERS_IN_PROJECT_KEY, ContainerUtil.<ProjectManagerListener>createLockFreeCopyOnWriteList());
}
listeners.add(listener);
}
@Override
public void removeProjectManagerListener(@NotNull Project project, @NotNull ProjectManagerListener listener) {
List<ProjectManagerListener> listeners = project.getUserData(LISTENERS_IN_PROJECT_KEY);
LOG.assertTrue(listeners != null);
boolean removed = listeners.remove(listener);
LOG.assertTrue(removed);
}
public void fireProjectOpened(Project project) {
if (LOG.isDebugEnabled()) {
LOG.debug("projectOpened");
}
for (ProjectManagerListener listener : myListeners) {
try {
listener.projectOpened(project);
}
catch (Exception e) {
LOG.error(e);
}
}
}
private void fireProjectClosed(Project project) {
if (LOG.isDebugEnabled()) {
LOG.debug("projectClosed");
}
for (ProjectManagerListener listener : myListeners) {
try {
listener.projectClosed(project);
}
catch (Exception e) {
LOG.error(e);
}
}
}
@Override
public boolean canClose(Project project) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: canClose()");
}
for (ProjectManagerListener listener : myListeners) {
try {
if (!listener.canCloseProject(project)) return false;
}
catch (Throwable e) {
LOG.warn(e); // DO NOT LET ANY PLUGIN to prevent closing due to exception
}
}
return true;
}
private static boolean ensureCouldCloseIfUnableToSave(@NotNull final Project project) {
final ProjectImpl.UnableToSaveProjectNotification[] notifications =
NotificationsManager.getNotificationsManager().getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, project);
if (notifications.length == 0) return true;
final String fileNames = StringUtil.join(notifications[0].getFileNames(), "\n");
final String msg = String.format("%s was unable to save some project files,\nare you sure you want to close this project anyway?",
ApplicationNamesInfo.getInstance().getProductName());
return Messages.showDialog(project, msg, "Unsaved Project", "Read-only files:\n\n" + fileNames, new String[]{"Yes", "No"}, 0, 1,
Messages.getWarningIcon()) == 0;
}
@Nullable
@Override
public Element getState() {
if (myDefaultProject != null) {
myDefaultProject.save();
}
if (myDefaultProjectRootElement == null) {
// we are not ready to save
return null;
}
Element element = new Element("state");
myDefaultProjectRootElement.detach();
element.addContent(myDefaultProjectRootElement);
return element;
}
@Override
public void loadState(Element state) {
myDefaultProjectRootElement = state.getChild("defaultProject");
if (myDefaultProjectRootElement != null) {
myDefaultProjectRootElement.detach();
}
}
public void setDefaultProjectRootElement(@NotNull Element defaultProjectRootElement) {
myDefaultProjectRootElement = defaultProjectRootElement;
}
}