blob: fc7d33b2e445f0576779745909fa7f9833fc3888 [file] [log] [blame]
/*
* 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.application.impl;
import com.intellij.BundleBase;
import com.intellij.CommonBundle;
import com.intellij.diagnostic.PerformanceWatcher;
import com.intellij.diagnostic.PluginException;
import com.intellij.diagnostic.ThreadDumper;
import com.intellij.ide.*;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.idea.StartupUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.impl.ApplicationPathMacroManager;
import com.intellij.openapi.components.impl.PlatformComponentManagerImpl;
import com.intellij.openapi.components.impl.stores.IApplicationStore;
import com.intellij.openapi.components.impl.stores.IComponentStore;
import com.intellij.openapi.components.impl.stores.StoreUtil;
import com.intellij.openapi.components.impl.stores.StoresFactory;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ex.ProjectEx;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.project.impl.ProjectManagerImpl;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.MessageDialogBuilder;
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.CharsetToolkit;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.PsiLock;
import com.intellij.ui.Splash;
import com.intellij.util.*;
import com.intellij.util.containers.Stack;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.ide.PooledThreadExecutor;
import org.picocontainer.MutablePicoContainer;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ApplicationImpl extends PlatformComponentManagerImpl implements ApplicationEx {
private static final Logger LOG = Logger.getInstance("#com.intellij.application.impl.ApplicationImpl");
private final ModalityState MODALITY_STATE_NONE = ModalityState.NON_MODAL;
// about writer preference: the way the j.u.c.l.ReentrantReadWriteLock.NonfairSync is implemented, the
// writer thread will be always at the queue head and therefore, j.u.c.l.ReentrantReadWriteLock.NonfairSync.readerShouldBlock()
// will return true if the write action is pending, exactly as we need
private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock(false);
private final ModalityInvokator myInvokator = new ModalityInvokatorImpl();
private final EventDispatcher<ApplicationListener> myDispatcher = EventDispatcher.create(ApplicationListener.class);
private IApplicationStore myComponentStore;
private final boolean myTestModeFlag;
private final boolean myHeadlessMode;
private final boolean myCommandLineMode;
private final boolean myIsInternal;
private final String myName;
private final Stack<Class> myWriteActionsStack = new Stack<Class>(); // accessed from EDT only, no need to sync
private volatile Runnable myExceptionalThreadWithReadAccessRunnable;
private int myInEditorPaintCounter; // EDT only
private final long myStartTime;
@Nullable
private final Splash mySplash;
private boolean myDoNotSave;
private volatile boolean myDisposeInProgress;
private final Disposable myLastDisposable = Disposer.newDisposable(); // will be disposed last
private final AtomicBoolean mySaveSettingsIsInProgress = new AtomicBoolean(false);
@SuppressWarnings({"UseOfArchaicSystemPropertyAccessors"})
private static final int ourDumpThreadsOnLongWriteActionWaiting = Integer.getInteger("dump.threads.on.long.write.action.waiting", 0);
private final ExecutorService ourThreadExecutorsService = PooledThreadExecutor.INSTANCE;
private boolean myIsFiringLoadingEvent = false;
private boolean myLoaded = false;
@NonNls private static final String WAS_EVER_SHOWN = "was.ever.shown";
private Boolean myActive;
private static final int IS_EDT_FLAG = 1<<30; // we don't mess with sign bit since we want to do arithmetic
private static final int IS_READ_LOCK_ACQUIRED_FLAG = 1<<29;
private static final int IS_EXCEPTIONAL_THREAD_FLAG = 1<<28;
private static class Status {
// higher three bits are for IS_* flags
// lower bits are for edtSafe counter
private int flags;
}
private static final ThreadLocal<Status> status = new ThreadLocal<Status>(){
@Override
protected Status initialValue() {
Status status = new Status();
status.flags = BitUtil.set(status.flags, IS_EDT_FLAG, EventQueue.isDispatchThread());
return status;
}
};
private static Status getStatus() {
return status.get();
}
private static void setReadLockAcquired(Status status, boolean acquired) {
status.flags = BitUtil.set(status.flags, IS_READ_LOCK_ACQUIRED_FLAG, acquired);
}
@NonNls private static final ModalityState ANY = new ModalityState() {
@Override
public boolean dominates(@NotNull ModalityState anotherState) {
return false;
}
@NonNls
@Override
public String toString() {
return "ANY";
}
};
@Override
protected void bootstrapPicoContainer(@NotNull String name) {
super.bootstrapPicoContainer(name);
getPicoContainer().registerComponentImplementation(IComponentStore.class, StoresFactory.getApplicationStoreClass());
getPicoContainer().registerComponentImplementation(ApplicationPathMacroManager.class);
}
@NotNull
public synchronized IApplicationStore getStateStore() {
if (myComponentStore == null) {
myComponentStore = (IApplicationStore)getPicoContainer().getComponentInstance(IComponentStore.class);
}
return myComponentStore;
}
@Override
public void initializeComponent(@NotNull Object component, boolean service) {
getStateStore().initComponent(component, service);
}
public ApplicationImpl(boolean isInternal,
boolean isUnitTestMode,
boolean isHeadless,
boolean isCommandLine,
@NotNull String appName,
@Nullable Splash splash) {
super(null);
ApplicationManager.setApplication(this, myLastDisposable); // reset back to null only when all components already disposed
getPicoContainer().registerComponentInstance(Application.class, this);
BundleBase.assertKeyIsFound = IconLoader.STRICT = isUnitTestMode || isInternal;
AWTExceptionHandler.register(); // do not crash AWT on exceptions
String debugDisposer = System.getProperty("idea.disposer.debug");
Disposer.setDebugMode((isInternal || isUnitTestMode || "on".equals(debugDisposer)) && !"off".equals(debugDisposer));
myStartTime = System.currentTimeMillis();
mySplash = splash;
myName = appName;
myIsInternal = isInternal;
myTestModeFlag = isUnitTestMode;
myHeadlessMode = isHeadless;
myCommandLineMode = isCommandLine;
myDoNotSave = isUnitTestMode || isHeadless;
loadApplicationComponents();
if (myTestModeFlag) {
registerShutdownHook();
}
if (!isUnitTestMode && !isHeadless) {
Disposer.register(this, Disposer.newDisposable(), "ui");
StartupUtil.addExternalInstanceListener(new Consumer<List<String>>() {
@Override
public void consume(final List<String> args) {
invokeLater(new Runnable() {
@Override
public void run() {
LOG.info("ApplicationImpl.externalInstanceListener invocation");
String currentDirectory = args.isEmpty() ? null : args.get(0);
List<String> realArgs = args.isEmpty() ? args : args.subList(1, args.size());
final Project project = CommandLineProcessor.processExternalCommandLine(realArgs, currentDirectory);
final JFrame frame;
if (project != null) {
frame = (JFrame)WindowManager.getInstance().getIdeFrame(project);
}
else {
frame = WindowManager.getInstance().findVisibleFrame();
}
if (frame != null) frame.requestFocus();
}
});
}
});
WindowsCommandLineProcessor.LISTENER = new WindowsCommandLineListener() {
@Override
public void processWindowsLauncherCommandLine(final String currentDirectory, final String commandLine) {
LOG.info("Received external Windows command line: current directory " + currentDirectory + ", command line " + commandLine);
invokeLater(new Runnable() {
@Override
public void run() {
final List<String> args = StringUtil.splitHonorQuotes(commandLine, ' ');
args.remove(0); // process name
CommandLineProcessor.processExternalCommandLine(args, currentDirectory);
}
});
}
};
}
}
private void registerShutdownHook() {
ShutDownTracker.getInstance(); // Necessary to avoid creating an instance while already shutting down.
ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
@Override
public void run() {
if (isDisposed() || myDisposeInProgress) {
return;
}
ShutDownTracker.invokeAndWait(isUnitTestMode(), true, new Runnable() {
@Override
public void run() {
if (ApplicationManager.getApplication() != ApplicationImpl.this) return;
try {
myDisposeInProgress = true;
saveAll();
}
finally {
if (!disposeSelf(true)) {
myDisposeInProgress = false;
}
}
}
});
}
});
}
private boolean disposeSelf(final boolean checkCanCloseProject) {
final ProjectManagerImpl manager = (ProjectManagerImpl)ProjectManagerEx.getInstanceEx();
if (manager != null) {
final boolean[] canClose = {true};
for (final Project project : manager.getOpenProjects()) {
try {
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
if (!manager.closeProject(project, true, true, checkCanCloseProject)) {
canClose[0] = false;
}
}
}, ApplicationBundle.message("command.exit"), null);
}
catch (Throwable e) {
LOG.error(e);
}
if (!canClose[0]) {
return false;
}
}
}
runWriteAction(new Runnable() {
@Override
public void run() {
Disposer.dispose(ApplicationImpl.this);
}
});
Disposer.assertIsEmpty();
return true;
}
@Override
@NotNull
public String getName() {
return myName;
}
@Override
public boolean holdsReadLock() {
return holdsReadLock(getStatus());
}
private static boolean holdsReadLock(Status status) {
return BitUtil.isSet(status.flags, IS_READ_LOCK_ACQUIRED_FLAG);
}
private void loadApplicationComponents() {
PluginManagerCore.initPlugins(mySplash);
IdeaPluginDescriptor[] plugins = PluginManagerCore.getPlugins();
for (IdeaPluginDescriptor plugin : plugins) {
if (!PluginManagerCore.shouldSkipPlugin(plugin)) {
loadComponentsConfiguration(plugin.getAppComponents(), plugin, false);
}
}
}
@Override
protected synchronized Object createComponent(@NotNull Class componentInterface) {
Object component = super.createComponent(componentInterface);
if (mySplash != null) {
mySplash.showProgress("", 0.65f + getPercentageOfComponentsLoaded() * 0.35f);
}
return component;
}
@NotNull
@Override
protected MutablePicoContainer createPicoContainer() {
return Extensions.getRootArea().getPicoContainer();
}
@Override
public boolean isInternal() {
return myIsInternal;
}
@Override
public boolean isEAP() {
return ApplicationInfoImpl.getShadowInstance().isEAP();
}
@Override
public boolean isUnitTestMode() {
return myTestModeFlag;
}
@Override
public boolean isHeadlessEnvironment() {
return myHeadlessMode;
}
@Override
public boolean isCommandLine() {
return myCommandLineMode;
}
@NotNull
@Override
public Future<?> executeOnPooledThread(@NotNull final Runnable action) {
return ourThreadExecutorsService.submit(new Runnable() {
@Override
public void run() {
if (isReadAccessAllowed()) {
assert false : describe(Thread.currentThread());
}
try {
action.run();
}
catch (ProcessCanceledException e) {
// ignore
}
catch (Throwable t) {
LOG.error(t);
}
finally {
//ReflectionUtil.resetThreadLocals();
Thread.interrupted(); // reset interrupted status
if (isReadAccessAllowed()) {
assert false : describe(Thread.currentThread());
}
}
}
});
}
@NotNull
@Override
public <T> Future<T> executeOnPooledThread(@NotNull final Callable<T> action) {
return ourThreadExecutorsService.submit(new Callable<T>() {
@Override
public T call() {
if (isReadAccessAllowed()) {
assert false : describe(Thread.currentThread());
}
try {
return action.call();
}
catch (ProcessCanceledException e) {
// ignore
}
catch (Throwable t) {
LOG.error(t);
}
finally {
//ReflectionUtil.resetThreadLocals();
Thread.interrupted(); // reset interrupted status
if (isReadAccessAllowed()) {
assert false : describe(Thread.currentThread());
}
}
return null;
}
});
}
@Override
public boolean isDispatchThread() {
return isDispatchThread(getStatus());
}
private static boolean isDispatchThread(Status status) {
return BitUtil.isSet(status.flags, IS_EDT_FLAG);
}
@Override
@NotNull
public ModalityInvokator getInvokator() {
return myInvokator;
}
@Override
public void invokeLater(@NotNull final Runnable runnable) {
myInvokator.invokeLater(runnable);
}
@Override
public void invokeLater(@NotNull final Runnable runnable, @NotNull final Condition expired) {
myInvokator.invokeLater(runnable, expired);
}
@Override
public void invokeLater(@NotNull final Runnable runnable, @NotNull final ModalityState state) {
myInvokator.invokeLater(runnable, state);
}
@Override
public void invokeLater(@NotNull final Runnable runnable, @NotNull final ModalityState state, @NotNull final Condition expired) {
myInvokator.invokeLater(runnable, state, expired);
}
@Override
public void load(@Nullable String optionsPath) throws IOException {
load(PathManager.getConfigPath(), optionsPath == null ? PathManager.getOptionsPath() : optionsPath);
}
public void load(@NotNull String configPath, @NotNull String optionsPath) throws IOException {
IApplicationStore store = getStateStore();
store.setOptionsPath(optionsPath);
store.setConfigPath(configPath);
myIsFiringLoadingEvent = true;
try {
fireBeforeApplicationLoaded();
}
finally {
myIsFiringLoadingEvent = false;
}
HeavyProcessLatch.INSTANCE.processStarted();
try {
store.load();
}
catch (StateStorageException e) {
throw new IOException(e.getMessage());
}
finally {
HeavyProcessLatch.INSTANCE.processFinished();
}
myLoaded = true;
createLocatorFile();
}
private static void createLocatorFile() {
File locatorFile = new File(PathManager.getSystemPath() + "/" + ApplicationEx.LOCATOR_FILE_NAME);
try {
byte[] data = PathManager.getHomePath().getBytes(CharsetToolkit.UTF8_CHARSET);
FileUtil.writeToFile(locatorFile, data);
}
catch (IOException e) {
LOG.warn("can't store a location in '" + locatorFile + "'", e);
}
}
@Override
public boolean isLoaded() {
return myLoaded;
}
@Override
protected <T> T getComponentFromContainer(@NotNull final Class<T> interfaceClass) {
if (myIsFiringLoadingEvent) {
return null;
}
return super.getComponentFromContainer(interfaceClass);
}
private void fireBeforeApplicationLoaded() {
for (ApplicationLoadListener listener : ApplicationLoadListener.EP_NAME.getExtensions()) {
try {
listener.beforeApplicationLoaded(this);
}
catch (Exception e) {
LOG.error(e);
}
}
}
@Override
public void dispose() {
fireApplicationExiting();
ShutDownTracker.getInstance().ensureStopperThreadsFinished();
disposeComponents();
ourThreadExecutorsService.shutdownNow();
myComponentStore = null;
super.dispose();
Disposer.dispose(myLastDisposable); // dispose it last
}
@Override
public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process,
@NotNull String progressTitle,
boolean canBeCanceled,
Project project) {
return runProcessWithProgressSynchronously(process, progressTitle, canBeCanceled, project, null);
}
@Override
public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process,
@NotNull final String progressTitle,
final boolean canBeCanceled,
@Nullable final Project project,
final JComponent parentComponent) {
return runProcessWithProgressSynchronously(process, progressTitle, canBeCanceled, project, parentComponent, null);
}
@Override
public boolean runProcessWithProgressSynchronously(@NotNull final Runnable process,
@NotNull final String progressTitle,
final boolean canBeCanceled,
@Nullable final Project project,
final JComponent parentComponent,
final String cancelText) {
assertIsDispatchThread();
if (myExceptionalThreadWithReadAccessRunnable != null ||
isUnitTestMode() ||
isHeadlessEnvironment()
) {
try {
ProgressManager.getInstance().runProcess(process, new EmptyProgressIndicator());
}
catch (ProcessCanceledException e) {
// ok to ignore.
return false;
}
return true;
}
final ProgressWindow progress = new ProgressWindow(canBeCanceled, false, project, parentComponent, cancelText);
// in case of abrupt application exit when 'ProgressManager.getInstance().runProcess(process, progress)' below
// does not have a chance to run, and as a result the progress won't be disposed
Disposer.register(this, progress);
progress.setTitle(progressTitle);
try {
myExceptionalThreadWithReadAccessRunnable = process;
final AtomicBoolean threadStarted = new AtomicBoolean();
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (myExceptionalThreadWithReadAccessRunnable != process) {
LOG.error("myExceptionalThreadWithReadAccessRunnable != process, process = " + myExceptionalThreadWithReadAccessRunnable);
}
executeOnPooledThread(new Runnable() {
@Override
public void run() {
if (myExceptionalThreadWithReadAccessRunnable != process) {
LOG.error("myExceptionalThreadWithReadAccessRunnable != process, process = " + myExceptionalThreadWithReadAccessRunnable);
}
setExceptionalThreadWithReadAccessFlag(true);
try {
LOG.assertTrue(isReadAccessAllowed());
ProgressManager.getInstance().runProcess(process, progress);
}
catch (ProcessCanceledException e) {
progress.cancel();
// ok to ignore.
}
catch (RuntimeException e) {
progress.cancel();
throw e;
}
finally {
setExceptionalThreadWithReadAccessFlag(false);
}
}
});
threadStarted.set(true);
}
});
progress.startBlocking();
LOG.assertTrue(threadStarted.get());
LOG.assertTrue(!progress.isRunning());
}
finally {
myExceptionalThreadWithReadAccessRunnable = null;
}
return !progress.isCanceled();
}
@Override
public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) {
Status status = getStatus();
if (isDispatchThread(status)) {
runnable.run();
return;
}
if (!isExceptionalThreadWithReadAccess(status) && holdsReadLock(status)) {
LOG.error("Calling invokeAndWait from read-action leads to possible deadlock.");
}
LaterInvocator.invokeAndWait(runnable, modalityState);
}
@Override
@NotNull
public ModalityState getCurrentModalityState() {
Object[] entities = LaterInvocator.getCurrentModalEntities();
return entities.length > 0 ? new ModalityStateEx(entities) : getNoneModalityState();
}
@Override
@NotNull
public ModalityState getModalityStateForComponent(@NotNull Component c) {
Window window = c instanceof Window ? (Window)c : SwingUtilities.windowForComponent(c);
if (window == null) return getNoneModalityState(); //?
return LaterInvocator.modalityStateForWindow(window);
}
@Override
public ModalityState getAnyModalityState() {
return ANY;
}
@Override
@NotNull
public ModalityState getDefaultModalityState() {
if (isDispatchThread()) {
return getCurrentModalityState();
}
ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
return progress == null ? getNoneModalityState() : progress.getModalityState();
}
@Override
@NotNull
public ModalityState getNoneModalityState() {
return MODALITY_STATE_NONE;
}
@Override
public long getStartTime() {
return myStartTime;
}
@Override
public long getIdleTime() {
return IdeEventQueue.getInstance().getIdleTime();
}
@Override
public void exit() {
exit(false, false);
}
@Override
public void exit(boolean force, final boolean exitConfirmed) {
exit(false, exitConfirmed, true, false);
}
@Override
public void restart() {
restart(false);
}
@Override
public void restart(final boolean exitConfirmed) {
exit(false, exitConfirmed, true, true);
}
/*
* There are two ways we can get an exit notification.
* 1. From user input i.e. ExitAction
* 2. From the native system.
* We should not process any quit notifications if we are handling another one
*
* Note: there are possible scenarios when we get a quit notification at a moment when another
* quit message is shown. In that case, showing multiple messages sounds contra-intuitive as well
*/
private static volatile boolean exiting = false;
public void exit(final boolean force, final boolean exitConfirmed, final boolean allowListenersToCancel, final boolean restart) {
if (exiting) return;
exiting = true;
try {
if (!force && !exitConfirmed && getDefaultModalityState() != ModalityState.NON_MODAL) {
return;
}
Runnable runnable = new Runnable() {
@Override
public void run() {
if (!force && !confirmExitIfNeeded(exitConfirmed)) {
saveAll();
return;
}
getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).appClosing();
myDisposeInProgress = true;
doExit(allowListenersToCancel, restart);
myDisposeInProgress = false;
}
};
if (isDispatchThread()) {
runnable.run();
}
else {
invokeLater(runnable, ModalityState.NON_MODAL);
}
}
finally {
exiting = false;
}
}
private boolean doExit(boolean allowListenersToCancel, boolean restart) {
saveSettings();
if (allowListenersToCancel && !canExit()) {
return false;
}
final boolean success = disposeSelf(allowListenersToCancel);
if (!success || isUnitTestMode()) {
return false;
}
int exitCode = 0;
if (restart && Restarter.isSupported()) {
try {
exitCode = Restarter.scheduleRestart();
}
catch (IOException e) {
LOG.warn("Cannot restart", e);
}
}
System.exit(exitCode);
return true;
}
private static boolean confirmExitIfNeeded(boolean exitConfirmed) {
final boolean hasUnsafeBgTasks = ProgressManager.getInstance().hasUnsafeProgressIndicator();
if (exitConfirmed && !hasUnsafeBgTasks) {
return true;
}
DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() {
@Override
public boolean isToBeShown() {
return GeneralSettings.getInstance().isConfirmExit() && ProjectManager.getInstance().getOpenProjects().length > 0;
}
@Override
public void setToBeShown(boolean value, int exitCode) {
GeneralSettings.getInstance().setConfirmExit(value);
}
@Override
public boolean canBeHidden() {
return !hasUnsafeBgTasks;
}
@Override
public boolean shouldSaveOptionsOnCancel() {
return false;
}
@NotNull
@Override
public String getDoNotShowMessage() {
return "Do not ask me again";
}
};
if (hasUnsafeBgTasks || option.isToBeShown()) {
String message = ApplicationBundle
.message(hasUnsafeBgTasks ? "exit.confirm.prompt.tasks" : "exit.confirm.prompt",
ApplicationNamesInfo.getInstance().getFullProductName());
if (MessageDialogBuilder.yesNo(ApplicationBundle.message("exit.confirm.title"), message).yesText(ApplicationBundle.message("command.exit")).noText(CommonBundle.message("button.cancel"))
.doNotAsk(option).show() != Messages.YES) {
return false;
}
}
return true;
}
private boolean canExit() {
for (ApplicationListener applicationListener : myDispatcher.getListeners()) {
if (!applicationListener.canExitApplication()) {
return false;
}
}
ProjectManagerEx projectManager = (ProjectManagerEx)ProjectManager.getInstance();
Project[] projects = projectManager.getOpenProjects();
for (Project project : projects) {
if (!projectManager.canClose(project)) {
return false;
}
}
return true;
}
@Override
public void runReadAction(@NotNull final Runnable action) {
Status status = getStatus();
if (isReadAccessAllowed(status)) {
action.run();
}
else {
assertNoPsiLock();
try {
myLock.readLock().lockInterruptibly();
setReadLockAcquired(status, true);
}
catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
try {
action.run();
}
finally {
setReadLockAcquired(status, false);
myLock.readLock().unlock();
}
}
}
@Override
public <T> T runReadAction(@NotNull final Computable<T> computation) {
Status status = getStatus();
if (isReadAccessAllowed(status)) {
return computation.compute();
}
assertNoPsiLock();
try {
myLock.readLock().lockInterruptibly();
setReadLockAcquired(status, true);
}
catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
try {
return computation.compute();
}
finally {
setReadLockAcquired(status, false);
myLock.readLock().unlock();
}
}
@Override
public <T, E extends Throwable> T runReadAction(@NotNull ThrowableComputable<T, E> computation) throws E {
Status status = getStatus();
if (isReadAccessAllowed(status)) {
return computation.compute();
}
assertNoPsiLock();
try {
myLock.readLock().lockInterruptibly();
setReadLockAcquired(status, true);
}
catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
try {
return computation.compute();
}
finally {
setReadLockAcquired(status, false);
myLock.readLock().unlock();
}
}
private static boolean isExceptionalThreadWithReadAccess(Status status) {
return BitUtil.isSet(status.flags, IS_EXCEPTIONAL_THREAD_FLAG);
}
public static void setExceptionalThreadWithReadAccessFlag(boolean flag) {
Status status = getStatus();
assert !isDispatchThread(status) : "exceptional thread must not be EDT";
assert flag != isReadAccessAllowed(status) : "must not start exceptional thread from inside read action";
status.flags = BitUtil.set(status.flags, IS_EXCEPTIONAL_THREAD_FLAG, flag);
}
@Override
public void runWriteAction(@NotNull final Runnable action) {
final AccessToken token = acquireWriteActionLock(action.getClass());
try {
action.run();
}
finally {
token.finish();
}
}
@Override
public <T> T runWriteAction(@NotNull final Computable<T> computation) {
final AccessToken token = acquireWriteActionLock(computation.getClass());
try {
return computation.compute();
}
finally {
token.finish();
}
}
@Override
public <T, E extends Throwable> T runWriteAction(@NotNull ThrowableComputable<T, E> computation) throws E {
final AccessToken token = acquireWriteActionLock(computation.getClass());
try {
return computation.compute();
}
finally {
token.finish();
}
}
@Override
public boolean hasWriteAction(@Nullable Class<?> actionClass) {
assertCanRunWriteAction(getStatus());
for (int i = myWriteActionsStack.size() - 1; i >= 0; i--) {
Class action = myWriteActionsStack.get(i);
if (actionClass == action || action != null && actionClass != null && ReflectionUtil.isAssignable(actionClass, action)) return true;
}
return false;
}
@Override
public void assertReadAccessAllowed() {
if (!isReadAccessAllowed()) {
LOG.error(
"Read access is allowed from event dispatch thread or inside read-action only" +
" (see com.intellij.openapi.application.Application.runReadAction())",
"Current thread: " + describe(Thread.currentThread()), "; dispatch thread: " + EventQueue.isDispatchThread() +"; isDispatchThread(): "+isDispatchThread(),
"SystemEventQueueThread: " + describe(getEventQueueThread()));
}
}
@NonNls
private static String describe(Thread o) {
if (o == null) return "null";
return o + " " + System.identityHashCode(o);
}
private static Thread getEventQueueThread() {
EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
try {
Method method = ReflectionUtil.getDeclaredMethod(EventQueue.class, "getDispatchThread");
return (Thread)method.invoke(eventQueue);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isReadAccessAllowed() {
return isReadAccessAllowed(getStatus());
}
private static boolean isReadAccessAllowed(Status status) {
return (status.flags & (IS_EDT_FLAG | IS_EXCEPTIONAL_THREAD_FLAG | IS_READ_LOCK_ACQUIRED_FLAG)) != 0;
}
private static void assertCanRunWriteAction(Status status) {
assertIsDispatchThread(status, "Write access is allowed from event dispatch thread only");
}
@Override
public void assertIsDispatchThread() {
assertIsDispatchThread(getStatus());
}
private static void assertIsDispatchThread(Status status) {
if (isDispatchThread(status)) return;
if (ShutDownTracker.isShutdownHookRunning()) return;
int safeCounter = getSafeCounter(status);
if (safeCounter == 0) {
assertIsDispatchThread(status, "Access is allowed from event dispatch thread only.");
}
}
private static int getSafeCounter(Status status) {
return status.flags & 0x1fffffff;
}
private static void assertIsDispatchThread(Status status, @NotNull String message) {
if (isDispatchThread(status)) return;
LOG.error(message,
"EventQueue.isDispatchThread()=" + EventQueue.isDispatchThread(),
"isDispatchThread()=" + isDispatchThread(getStatus()),
"Toolkit.getEventQueue()=" + Toolkit.getDefaultToolkit().getSystemEventQueue(),
"Current thread: " + describe(Thread.currentThread()),
"SystemEventQueueThread: " + describe(getEventQueueThread()) + "\n" + ThreadDumper.dumpThreadsToString() + "\n-----------");
}
@Override
public void runEdtSafeAction(@NotNull Runnable runnable) {
Status status = getStatus();
LOG.assertTrue(getSafeCounter(status) < 1<<26);
status.flags++;
try {
runnable.run();
}
finally {
status.flags--;
}
}
@Override
public void assertIsDispatchThread(@Nullable final JComponent component) {
if (component == null) return;
Status status = getStatus();
if (isDispatchThread(status)) {
return;
}
if (Boolean.TRUE.equals(component.getClientProperty(WAS_EVER_SHOWN))) {
assertIsDispatchThread(status);
}
else {
final JRootPane root = component.getRootPane();
if (root != null) {
component.putClientProperty(WAS_EVER_SHOWN, Boolean.TRUE);
assertIsDispatchThread(status);
}
}
}
@Override
public void assertTimeConsuming() {
if (myTestModeFlag || myHeadlessMode || ShutDownTracker.isShutdownHookRunning()) return;
LOG.assertTrue(!isDispatchThread(), "This operation is time consuming and must not be called on EDT");
}
@Override
public boolean tryRunReadAction(@NotNull Runnable action) {
Status status = getStatus();
//if we are inside read action, do not try to acquire read lock again since it will deadlock if there is a pending writeAction
boolean mustAcquire = !isReadAccessAllowed(status);
if (mustAcquire) {
assertNoPsiLock();
try {
// timed version of tryLock() respects fairness unlike the no-args method
if (!myLock.readLock().tryLock(0, TimeUnit.MILLISECONDS)) return false;
setReadLockAcquired(status, true);
}
catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
try {
action.run();
}
finally {
if (mustAcquire) {
setReadLockAcquired(status, false);
myLock.readLock().unlock();
}
}
return true;
}
public boolean tryToApplyActivationState(boolean active, Window window) {
final Component frame = UIUtil.findUltimateParent(window);
if (frame instanceof IdeFrame) {
final IdeFrame ideFrame = (IdeFrame)frame;
if (isActive() != active) {
myActive = active;
System.setProperty("idea.active", myActive.toString());
ApplicationActivationListener publisher = getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC);
if (active) {
publisher.applicationActivated(ideFrame);
}
else {
publisher.applicationDeactivated(ideFrame);
}
return true;
}
}
return false;
}
@Override
public boolean isActive() {
if (isUnitTestMode()) return true;
if (myActive == null) {
Window active = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
return active != null;
}
return myActive;
}
@NotNull
@Override
public AccessToken acquireReadActionLock() {
Status status = getStatus();
// if we are inside read action, do not try to acquire read lock again since it will deadlock if there is a pending writeAction
if (isReadAccessAllowed(status)) return AccessToken.EMPTY_ACCESS_TOKEN;
return new ReadAccessToken(status);
}
@NotNull
@Override
public AccessToken acquireWriteActionLock(Class clazz) {
return new WriteAccessToken(clazz);
}
private volatile boolean myWriteActionPending;
@Override
public boolean isWriteActionPending() {
return myWriteActionPending;
}
private class WriteAccessToken extends AccessToken {
private final Class clazz;
public WriteAccessToken(Class _clazz) {
clazz = _clazz;
assertCanRunWriteAction(getStatus());
boolean writeActionPending = myWriteActionPending;
myWriteActionPending = true;
try {
ActivityTracker.getInstance().inc();
fireBeforeWriteActionStart(clazz);
try {
if (!isWriteAccessAllowed()) {
assertNoPsiLock();
}
if (!myLock.writeLock().tryLock()) {
final AtomicBoolean lockAcquired = new AtomicBoolean(false);
if (ourDumpThreadsOnLongWriteActionWaiting > 0) {
executeOnPooledThread(new Runnable() {
@Override
public void run() {
while (!lockAcquired.get()) {
TimeoutUtil.sleep(ourDumpThreadsOnLongWriteActionWaiting);
if (!lockAcquired.get()) {
PerformanceWatcher.getInstance().dumpThreads(true);
}
}
}
});
}
myLock.writeLock().lockInterruptibly();
lockAcquired.set(true);
}
}
catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
finally {
myWriteActionPending = writeActionPending;
}
myWriteActionsStack.push(clazz);
fireWriteActionStarted(clazz);
}
@Override
public void finish() {
try {
fireWriteActionFinished(clazz);
myWriteActionsStack.pop();
}
finally {
myLock.writeLock().unlock();
released();
}
}
@Override
protected void acquired() {
String id = id();
if (id != null) {
final Thread thread = Thread.currentThread();
thread.setName(thread.getName() + id);
}
}
@Override
protected void released() {
String id = id();
if (id != null) {
final Thread thread = Thread.currentThread();
String name = thread.getName();
name = StringUtil.replace(name, id, "");
thread.setName(name);
}
}
private String id() {
Class aClass = getClass();
String name = aClass.getName();
while (name == null) {
aClass = aClass.getSuperclass();
name = aClass.getName();
}
name = name.substring(name.lastIndexOf('.') + 1);
name = name.substring(name.lastIndexOf('$') + 1);
if (!name.equals("AccessToken")) {
return " [" + name+"]";
}
return null;
}
}
private class ReadAccessToken extends AccessToken {
private final Status myStatus;
private ReadAccessToken(Status status) {
myStatus = status;
assertNoPsiLock();
try {
myLock.readLock().lockInterruptibly();
setReadLockAcquired(myStatus, true);
acquired();
}
catch (InterruptedException e) {
throw new RuntimeInterruptedException(e);
}
}
@Override
public void finish() {
setReadLockAcquired(myStatus, false);
myLock.readLock().unlock();
released();
}
}
private final boolean myExtraChecks = isUnitTestMode();
private void assertNoPsiLock() {
if (myExtraChecks) {
LOG.assertTrue(!Thread.holdsLock(PsiLock.LOCK), "Thread must not hold PsiLock while performing readAction");
}
}
@Override
public void assertWriteAccessAllowed() {
LOG.assertTrue(isWriteAccessAllowed(),
"Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())");
}
@Override
public boolean isWriteAccessAllowed() {
return myLock.isWriteLockedByCurrentThread();
}
@Override
public boolean isWriteActionInProgress() {
return myLock.getWriteHoldCount() != 0;
}
public void editorPaintStart() {
myInEditorPaintCounter++;
}
public void editorPaintFinish() {
myInEditorPaintCounter--;
LOG.assertTrue(myInEditorPaintCounter >= 0);
}
@Override
public void addApplicationListener(@NotNull ApplicationListener l) {
myDispatcher.addListener(l);
}
@Override
public void addApplicationListener(@NotNull ApplicationListener l, @NotNull Disposable parent) {
myDispatcher.addListener(l, parent);
}
@Override
public void removeApplicationListener(@NotNull ApplicationListener l) {
myDispatcher.removeListener(l);
}
private void fireApplicationExiting() {
myDispatcher.getMulticaster().applicationExiting();
}
private void fireBeforeWriteActionStart(Class action) {
myDispatcher.getMulticaster().beforeWriteActionStart(action);
}
private void fireWriteActionStarted(Class action) {
myDispatcher.getMulticaster().writeActionStarted(action);
}
private void fireWriteActionFinished(Class action) {
myDispatcher.getMulticaster().writeActionFinished(action);
}
public void _saveSettings() { // public for testing purposes
if (mySaveSettingsIsInProgress.compareAndSet(false, true)) {
try {
StoreUtil.doSave(getStateStore());
}
catch (final Throwable e) {
if (isUnitTestMode()) {
System.out.println("Saving application settings failed");
e.printStackTrace();
}
else {
LOG.info("Saving application settings failed", e);
invokeLater(new Runnable() {
@Override
public void run() {
if (e instanceof PluginException) {
final PluginException pluginException = (PluginException)e;
PluginManagerCore.disablePlugin(pluginException.getPluginId().getIdString());
Messages.showMessageDialog("The plugin " +
pluginException.getPluginId() +
" failed to save settings and has been disabled. Please restart " +
ApplicationNamesInfo.getInstance().getFullProductName(), CommonBundle.getErrorTitle(),
Messages.getErrorIcon());
}
else {
Messages.showMessageDialog(ApplicationBundle.message("application.save.settings.error", e.getLocalizedMessage()),
CommonBundle.getErrorTitle(), Messages.getErrorIcon());
}
}
});
}
}
finally {
mySaveSettingsIsInProgress.set(false);
}
}
}
@Override
public void saveSettings() {
if (myDoNotSave) return;
_saveSettings();
}
@Override
public void saveAll() {
if (myDoNotSave) return;
FileDocumentManager.getInstance().saveAllDocuments();
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
for (Project openProject : openProjects) {
ProjectEx project = (ProjectEx)openProject;
project.save();
}
saveSettings();
}
@Override
public void doNotSave() {
doNotSave(true);
}
@Override
public void doNotSave(boolean value) {
myDoNotSave = value;
}
@Override
public boolean isDoNotSave() {
return myDoNotSave;
}
@NotNull
@Override
public <T> T[] getExtensions(@NotNull final ExtensionPointName<T> extensionPointName) {
return Extensions.getRootArea().getExtensionPoint(extensionPointName).getExtensions();
}
@Override
public boolean isDisposeInProgress() {
return myDisposeInProgress || ShutDownTracker.isShutdownHookRunning();
}
@Override
public boolean isRestartCapable() {
return Restarter.isSupported();
}
@Override
protected boolean logSlowComponents() {
return super.logSlowComponents() || ApplicationInfoImpl.getShadowInstance().isEAP();
}
@TestOnly
public void setDisposeInProgress(boolean disposeInProgress) {
myDisposeInProgress = disposeInProgress;
}
@NonNls
@Override
public String toString() {
return "Application" +
(isDisposed() ? " (Disposed)" : "") +
(isUnitTestMode() ? " (Unit test)" : "") +
(isInternal() ? " (Internal)" : "") +
(isHeadlessEnvironment() ? " (Headless)" : "") +
(isCommandLine() ? " (Command line)" : "");
}
}