@SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod"})
public class ApplicationImpl extends ComponentManagerImpl implements ApplicationEx {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.application.impl.ApplicationImpl");
  private final ModalityState MODALITY_STATE_NONE = ModalityState.NON_MODAL;
  private final ModalityInvokator myInvokator = new ModalityInvokatorImpl();

  private final EventDispatcher<ApplicationListener> myDispatcher =
      EventDispatcher.create(ApplicationListener.class);

  private IApplicationStore myComponentStore;

  private boolean myTestModeFlag;
  private final boolean myHeadlessMode;
  private final boolean myCommandLineMode;

  private final boolean myIsInternal;
  private final String myName;

  private final ReentrantWriterPreferenceReadWriteLock myActionsLock =
      new ReentrantWriterPreferenceReadWriteLock();
  private final Stack<Class> myWriteActionsStack =
      new Stack<Class>(); // accessed from EDT only, no need to sync

  private volatile Runnable myExceptionalThreadWithReadAccessRunnable;

  private int myInEditorPaintCounter = 0;
  private long myStartTime = 0;
  @Nullable private Splash mySplash;
  private boolean myDoNotSave;
  private volatile boolean myDisposeInProgress = false;

  private int myRestartCode = 0;
  private volatile int myExitCode = 0;
  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 =
      new ThreadPoolExecutor(
          3,
          Integer.MAX_VALUE,
          5 * 60L,
          TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(),
          new ThreadFactory() {
            int i;

            public Thread newThread(Runnable r) {
              final Thread thread =
                  new Thread(r, "ApplicationImpl pooled thread " + i++) {
                    public void interrupt() {
                      if (LOG.isDebugEnabled()) {
                        LOG.debug("Interrupted worker, will remove from pool");
                      }
                      super.interrupt();
                    }

                    public void run() {
                      try {
                        super.run();
                      } catch (Throwable t) {
                        if (LOG.isDebugEnabled()) {
                          LOG.debug("Worker exits due to exception", t);
                        }
                      }
                    }
                  };
              thread.setPriority(Thread.NORM_PRIORITY - 1);
              return thread;
            }
          });
  private boolean myIsFiringLoadingEvent = false;
  @NonNls private static final String WAS_EVER_SHOWN = "was.ever.shown";

  private Boolean myActive;

  private static final ThreadLocal<Integer> ourEdtSafe = new ThreadLocal<Integer>();
  private static final ModalityState ANY =
      new ModalityState() {
        @Override
        public boolean dominates(@NotNull ModalityState anotherState) {
          return false;
        }

        @Override
        public String toString() {
          return "ANY";
        }
      };

  protected void bootstrapPicoContainer() {
    super.bootstrapPicoContainer();
    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(Object component, boolean service) {
    getStateStore().initComponent(component, service);
  }

  public ApplicationImpl(
      boolean isInternal,
      boolean isUnitTestMode,
      boolean isHeadless,
      boolean isCommandLine,
      @NotNull String appName,
      Splash splash) {
    super(null);

    ApplicationManagerEx.setApplication(
        this, myLastDisposable); // reset back to null only when all components already disposed

    getPicoContainer().registerComponentInstance(Application.class, this);

    CommonBundle.assertKeyIsFound = isUnitTestMode;
    AWTExceptionHandler.register(); // do not crash AWT on exceptions
    if ((isInternal || isUnitTestMode)
        && !Comparing.equal("off", System.getProperty("idea.disposer.debug"))) {
      Disposer.setDebugMode(true);
    }
    myStartTime = System.currentTimeMillis();
    mySplash = splash;
    myName = appName;

    PluginsFacade.INSTANCE =
        new PluginsFacade() {
          public IdeaPluginDescriptor getPlugin(PluginId id) {
            return PluginManager.getPlugin(id);
          }

          public IdeaPluginDescriptor[] getPlugins() {
            return PluginManager.getPlugins();
          }
        };

    myIsInternal = isInternal;
    myTestModeFlag = isUnitTestMode;
    myHeadlessMode = isHeadless;
    myCommandLineMode = isCommandLine;

    myDoNotSave = myTestModeFlag || myHeadlessMode;

    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() {
                      final Project project = CommandLineProcessor.processExternalCommandLine(args);
                      final IdeFrame frame;
                      if (project != null) {
                        frame = WindowManager.getInstance().getIdeFrame(project);
                      } else {
                        frame = WindowManager.getInstance().getAllFrames()[0];
                      }
                      ((IdeFrameImpl) frame).requestFocus();
                    }
                  });
            }
          });
    }

    final String s = System.getProperty("jb.restart.code");
    if (s != null) {
      try {
        myRestartCode = Integer.parseInt(s);
      } catch (NumberFormatException ignore) {
      }
    }

    registerFont("/fonts/Inconsolata.ttf");
  }

  private void registerFont(String name) {
    if (isHeadlessEnvironment()) return;

    InputStream is = null;
    try {
      is = getClass().getResourceAsStream(name);
      final Font font = Font.createFont(Font.TRUETYPE_FONT, is);
      GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
    } catch (Exception e) {
      LOG.info(e);
    } finally {
      if (is != null) {
        try {
          is.close();
        } catch (IOException e) {
          LOG.error(e);
        }
      }
    }
  }

  private void registerShutdownHook() {
    ShutDownTracker
        .getInstance(); // Necessary to avoid creating an instance while already shutting down.

    ShutDownTracker.getInstance()
        .registerShutdownTask(
            new Runnable() {
              public void run() {
                if (isDisposed() || isDisposeInProgress()) {
                  return;
                }
                ShutDownTracker.invokeAndWait(
                    isUnitTestMode(),
                    new Runnable() {
                      public void run() {
                        if (ApplicationManager.getApplication() != ApplicationImpl.this) return;
                        try {
                          myDisposeInProgress = true;
                          saveAll();
                        } finally {
                          disposeSelf();
                        }
                      }
                    });
              }
            });
  }

  private boolean disposeSelf() {
    final CommandProcessor commandProcessor = CommandProcessor.getInstance();
    final Ref<Boolean> canClose = new Ref<Boolean>(Boolean.TRUE);
    for (final Project project : ProjectManagerEx.getInstanceEx().getOpenProjects()) {
      try {
        commandProcessor.executeCommand(
            project,
            new Runnable() {
              public void run() {
                canClose.set(ProjectUtil.closeAndDispose(project));
              }
            },
            ApplicationBundle.message("command.exit"),
            null);
      } catch (Throwable e) {
        LOG.error(e);
      }
      if (!canClose.get()) {
        myDisposeInProgress = false;
        return false;
      }
    }
    Disposer.dispose(this);

    Disposer.assertIsEmpty();
    return true;
  }

  @NotNull
  public String getName() {
    return myName;
  }

  public boolean holdsReadLock() {
    return myActionsLock.isReadLockAcquired();
  }

  @Override
  protected void handleInitComponentError(
      final Throwable ex, final boolean fatal, final String componentClassName) {
    if (PluginManager.isPluginClass(componentClassName)) {
      LOG.error(ex);
      PluginId pluginId = PluginManager.getPluginByClassName(componentClassName);
      @NonNls
      final String errorMessage =
          "Plugin "
              + pluginId.getIdString()
              + " failed to initialize and will be disabled:\n"
              + ex.getMessage()
              + "\nPlease restart "
              + ApplicationNamesInfo.getInstance().getFullProductName()
              + ".";
      PluginManager.disablePlugin(pluginId.getIdString());
      if (!myHeadlessMode) {
        JOptionPane.showMessageDialog(null, errorMessage);
      } else {
        //noinspection UseOfSystemOutOrSystemErr
        System.out.println(errorMessage);
        System.exit(1);
      }
      return; // do not call super
    }
    if (fatal) {
      LOG.error(ex);
      @NonNls
      final String errorMessage =
          "Fatal error initializing class "
              + componentClassName
              + ":\n"
              + ex.toString()
              + "\nComplete error stacktrace was written to idea.log";
      if (!myHeadlessMode) {
        JOptionPane.showMessageDialog(null, errorMessage);
      } else {
        //noinspection UseOfSystemOutOrSystemErr
        System.out.println(errorMessage);
      }
    }
    super.handleInitComponentError(ex, fatal, componentClassName);
  }

  private void loadApplicationComponents() {
    PluginManager.initPlugins(mySplash);
    final IdeaPluginDescriptor[] plugins = PluginManager.getPlugins();
    for (IdeaPluginDescriptor plugin : plugins) {
      if (PluginManager.shouldSkipPlugin(plugin)) continue;
      loadComponentsConfiguration(plugin.getAppComponents(), plugin, false);
    }
  }

  @Override
  protected synchronized Object createComponent(Class componentInterface) {
    Object component = super.createComponent(componentInterface);
    if (mySplash != null) {
      mySplash.showProgress(
          "", (float) (0.65f + myComponentsRegistry.getPercentageOfComponentsLoaded() * 0.35f));
    }
    return component;
  }

  protected MutablePicoContainer createPicoContainer() {
    return Extensions.getRootArea().getPicoContainer();
  }

  public boolean isInternal() {
    return myIsInternal;
  }

  public boolean isUnitTestMode() {
    return myTestModeFlag;
  }

  public void setUnitTestMode(boolean testModeFlag) {
    myTestModeFlag = testModeFlag;
  }

  public boolean isHeadlessEnvironment() {
    return myHeadlessMode;
  }

  public boolean isCommandLine() {
    return myCommandLineMode;
  }

  public Future<?> executeOnPooledThread(@NotNull final Runnable action) {
    return ourThreadExecutorsService.submit(
        new Runnable() {
          public void run() {
            try {
              action.run();
            } catch (ProcessCanceledException e) {
              // ignore
            } catch (Throwable t) {
              LOG.error(t);
            } finally {
              Thread.interrupted(); // reset interrupted status
            }
          }
        });
  }

  @Override
  public <T> Future<T> executeOnPooledThread(@NotNull final Callable<T> action) {
    return ourThreadExecutorsService.submit(
        new Callable<T>() {
          public T call() {
            try {
              return action.call();
            } catch (ProcessCanceledException e) {
              // ignore
            } catch (Throwable t) {
              LOG.error(t);
            } finally {
              Thread.interrupted(); // reset interrupted status
            }
            return null;
          }
        });
  }

  private static Thread ourDispatchThread = null;

  public boolean isDispatchThread() {
    return EventQueue.isDispatchThread();
  }

  @NotNull
  public ModalityInvokator getInvokator() {
    return myInvokator;
  }

  public void invokeLater(@NotNull final Runnable runnable) {
    myInvokator.invokeLater(runnable);
  }

  public void invokeLater(@NotNull final Runnable runnable, @NotNull final Condition expired) {
    myInvokator.invokeLater(runnable, expired);
  }

  public void invokeLater(@NotNull final Runnable runnable, @NotNull final ModalityState state) {
    myInvokator.invokeLater(runnable, state);
  }

  public void invokeLater(
      @NotNull final Runnable runnable,
      @NotNull final ModalityState state,
      @NotNull final Condition expired) {
    myInvokator.invokeLater(runnable, state, expired);
  }

  public void load(String path) throws IOException, InvalidDataException {
    getStateStore().setOptionsPath(path);
    getStateStore().setConfigPath(PathManager.getConfigPath());
    myIsFiringLoadingEvent = true;
    try {
      fireBeforeApplicationLoaded();
    } finally {
      myIsFiringLoadingEvent = false;
    }

    loadComponentRoamingTypes();

    HeavyProcessLatch.INSTANCE.processStarted();
    try {
      getStateStore().load();
    } catch (StateStorageException e) {
      throw new IOException(e.getMessage());
    } finally {
      HeavyProcessLatch.INSTANCE.processFinished();
    }
  }

  @Override
  protected <T> T getComponentFromContainer(final Class<T> interfaceClass) {
    if (myIsFiringLoadingEvent) {
      return null;
    }
    return super.getComponentFromContainer(interfaceClass);
  }

  private static void loadComponentRoamingTypes() {
    ExtensionPoint<RoamingTypeExtensionPointBean> point =
        Extensions.getRootArea().getExtensionPoint("com.intellij.ComponentRoamingType");
    final RoamingTypeExtensionPointBean[] componentRoamingTypes = point.getExtensions();

    for (RoamingTypeExtensionPointBean object : componentRoamingTypes) {

      assert object.componentName != null;
      assert object.roamingType != null;

      final RoamingType type = RoamingType.valueOf(object.roamingType);

      assert type != null;

      ComponentRoamingManager.getInstance().setRoamingType(object.componentName, type);
    }
  }

  private void fireBeforeApplicationLoaded() {
    ExtensionPoint<ApplicationLoadListener> point =
        Extensions.getRootArea().getExtensionPoint("com.intellij.ApplicationLoadListener");
    final ApplicationLoadListener[] objects = point.getExtensions();
    for (ApplicationLoadListener object : objects) {
      try {
        object.beforeApplicationLoaded(this);
      } catch (Exception e) {
        LOG.error(e);
      }
    }
  }

  public void dispose() {
    fireApplicationExiting();

    ShutDownTracker.getInstance().ensureStopperThreadsFinished();

    disposeComponents();

    ourThreadExecutorsService.shutdownNow();
    myComponentStore = null;
    super.dispose();
    Disposer.dispose(myLastDisposable); // dispose it last
  }

  private final Object lock = new Object();

  private void makeChangesVisibleToEDT() {
    synchronized (lock) {
      lock.hashCode();
    }
  }

  public boolean runProcessWithProgressSynchronously(
      @NotNull final Runnable process,
      @NotNull String progressTitle,
      boolean canBeCanceled,
      Project project) {
    return runProcessWithProgressSynchronously(
        process, progressTitle, canBeCanceled, project, null);
  }

  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);
  }

  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
        || ApplicationManager.getApplication().isUnitTestMode()
        || ApplicationManager.getApplication().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);
    progress.setTitle(progressTitle);

    try {
      myExceptionalThreadWithReadAccessRunnable = process;
      final boolean[] threadStarted = {false};
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              if (myExceptionalThreadWithReadAccessRunnable != process) {
                LOG.error(
                    "myExceptionalThreadWithReadAccessRunnable != process, process = "
                        + myExceptionalThreadWithReadAccessRunnable);
              }

              executeOnPooledThread(
                  new Runnable() {
                    public void run() {
                      if (myExceptionalThreadWithReadAccessRunnable != process) {
                        LOG.error(
                            "myExceptionalThreadWithReadAccessRunnable != process, process = "
                                + myExceptionalThreadWithReadAccessRunnable);
                      }

                      final boolean old = setExceptionalThreadWithReadAccessFlag(true);
                      LOG.assertTrue(isReadAccessAllowed());
                      try {
                        ProgressManager.getInstance().runProcess(process, progress);
                      } catch (ProcessCanceledException e) {
                        progress.cancel();
                        // ok to ignore.
                      } catch (RuntimeException e) {
                        progress.cancel();
                        throw e;
                      } finally {
                        setExceptionalThreadWithReadAccessFlag(old);
                        makeChangesVisibleToEDT();
                      }
                    }
                  });
              threadStarted[0] = true;
            }
          });

      progress.startBlocking();

      LOG.assertTrue(threadStarted[0]);
      LOG.assertTrue(!progress.isRunning());
    } finally {
      myExceptionalThreadWithReadAccessRunnable = null;
      makeChangesVisibleToEDT();
    }

    return !progress.isCanceled();
  }

  public boolean isInModalProgressThread() {
    if (myExceptionalThreadWithReadAccessRunnable == null || !isExceptionalThreadWithReadAccess()) {
      return false;
    }
    ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
    return progressIndicator.isModal()
        && ((ProgressIndicatorEx) progressIndicator).isModalityEntered();
  }

  public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) {
    if (isDispatchThread()) {
      LOG.error("invokeAndWait must not be called from event queue thread");
      runnable.run();
      return;
    }

    if (isExceptionalThreadWithReadAccess()) { // OK if we're in exceptional thread.
      LaterInvocator.invokeAndWait(runnable, modalityState);
      return;
    }

    if (myActionsLock.isReadLockAcquired()) {
      LOG.error("Calling invokeAndWait from read-action leads to possible deadlock.");
    }

    LaterInvocator.invokeAndWait(runnable, modalityState);
  }

  @NotNull
  public ModalityState getCurrentModalityState() {
    Object[] entities = LaterInvocator.getCurrentModalEntities();
    return entities.length > 0 ? new ModalityStateEx(entities) : getNoneModalityState();
  }

  @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;
  }

  @NotNull
  public ModalityState getDefaultModalityState() {
    if (EventQueue.isDispatchThread()) {
      return getCurrentModalityState();
    } else {
      ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
      return progress == null ? getNoneModalityState() : progress.getModalityState();
    }
  }

  @NotNull
  public ModalityState getNoneModalityState() {
    return MODALITY_STATE_NONE;
  }

  public long getStartTime() {
    return myStartTime;
  }

  public long getIdleTime() {
    return IdeEventQueue.getInstance().getIdleTime();
  }

  public void exit() {
    exit(false);
  }

  public void exit(final boolean force) {
    exit(force, true);
  }

  public void exit(final boolean force, final boolean allowListenersToCancel) {
    if (!force && getDefaultModalityState() != ModalityState.NON_MODAL) {
      return;
    }

    Runnable runnable =
        new Runnable() {
          public void run() {
            if (!force && !showConfirmation()) {
              saveAll();
              myExitCode = 0;
              return;
            }

            getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).appClosing();
            myDisposeInProgress = true;

            FileDocumentManager.getInstance().saveAllDocuments();

            saveSettings();

            if (!canExit() && allowListenersToCancel) {
              myExitCode = 0;
              return;
            }

            final boolean success = disposeSelf();
            if (!success || isUnitTestMode()) {
              myExitCode = 0;
              return;
            }

            System.exit(myExitCode);
          }
        };

    if (!isDispatchThread()) {
      invokeLater(runnable, ModalityState.NON_MODAL);
    } else {
      runnable.run();
    }
  }

  private static boolean showConfirmation() {
    final boolean hasUnsafeBgTasks = ProgressManager.getInstance().hasUnsafeProgressIndicator();

    DialogWrapper.DoNotAskOption option =
        new DialogWrapper.DoNotAskOption() {
          @Override
          public boolean isToBeShown() {
            return GeneralSettings.getInstance().isConfirmExit();
          }

          @Override
          public void setToBeShown(boolean value, int exitCode) {
            GeneralSettings.getInstance().setConfirmExit(value);
          }

          @Override
          public boolean canBeHidden() {
            return !hasUnsafeBgTasks;
          }

          @Override
          public boolean shouldSaveOptionsOnCancel() {
            return false;
          }

          @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 (DialogWrapper.OK_EXIT_CODE
          != Messages.showYesNoDialog(
              message,
              ApplicationBundle.message("exit.confirm.title"),
              ApplicationBundle.message("command.exit"),
              "Cancel",
              Messages.getQuestionIcon(),
              option)) {
        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;
  }

  public void runReadAction(@NotNull final Runnable action) {
    final AccessToken token = acquireReadActionLockImpl(false);

    try {
      action.run();
    } finally {
      token.finish();
    }
  }

  private static final ThreadLocal<Boolean> exceptionalThreadWithReadAccessFlag =
      new ThreadLocal<Boolean>();

  private static boolean isExceptionalThreadWithReadAccess() {
    Boolean flag = exceptionalThreadWithReadAccessFlag.get();
    return flag == Boolean.TRUE;
  }

  public static boolean setExceptionalThreadWithReadAccessFlag(boolean flag) {
    boolean old = isExceptionalThreadWithReadAccess();
    if (flag) {
      exceptionalThreadWithReadAccessFlag.set(Boolean.TRUE);
    } else {
      exceptionalThreadWithReadAccessFlag.remove();
    }
    return old;
  }

  public <T> T runReadAction(@NotNull final Computable<T> computation) {
    final AccessToken token = acquireReadActionLockImpl(false);

    try {
      return computation.compute();
    } finally {
      token.finish();
    }
  }

  public void runWriteAction(@NotNull final Runnable action) {
    final AccessToken token = acquireWriteActionLock(action.getClass());
    try {
      action.run();
    } finally {
      token.finish();
    }
  }

  public <T> T runWriteAction(@NotNull final Computable<T> computation) {
    final AccessToken token = acquireWriteActionLock(computation.getClass());
    try {
      return computation.compute();
    } finally {
      token.finish();
    }
  }

  public boolean hasWriteAction(@Nullable Class<?> actionClass) {
    assertCanRunWriteAction();

    for (int i = myWriteActionsStack.size() - 1; i >= 0; i--) {
      Class action = myWriteActionsStack.get(i);
      if (actionClass == action
          || action != null && ReflectionCache.isAssignable(actionClass, action)) return true;
    }
    return false;
  }

  public void assertReadAccessAllowed() {
    if (myHeadlessMode) return;
    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()),
          "Our dispatch thread:" + describe(ourDispatchThread),
          "SystemEventQueueThread: " + describe(getEventQueueThread()));
    }
  }

  @NonNls
  private static String describe(Thread o) {
    if (o == null) return "null";
    return o.toString() + " " + System.identityHashCode(o);
  }

  @Nullable
  private static Thread getEventQueueThread() {
    EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    try {
      Method method = EventQueue.class.getDeclaredMethod("getDispatchThread");
      method.setAccessible(true);
      return (Thread) method.invoke(eventQueue);
    } catch (Exception e1) {
      // ok
    }
    return null;
  }

  public boolean isReadAccessAllowed() {
    Thread currentThread = Thread.currentThread();
    return ourDispatchThread == currentThread
        || isExceptionalThreadWithReadAccess()
        || myActionsLock.isReadLockAcquired()
        || myActionsLock.isWriteLockAcquired()
        || isDispatchThread();
  }

  private static void assertCanRunWriteAction() {
    assertIsDispatchThread("Write access is allowed from event dispatch thread only");
  }

  public void assertIsDispatchThread() {
    assertIsDispatchThread("Access is allowed from event dispatch thread only.");
  }

  private static void assertIsDispatchThread(String message) {
    if (ShutDownTracker.isShutdownHookRunning()) return;
    final Thread currentThread = Thread.currentThread();
    if (ourDispatchThread == currentThread) return;

    if (EventQueue.isDispatchThread()) {
      ourDispatchThread = currentThread;
    }
    if (ourDispatchThread == currentThread) return;

    Integer safeCounter = ourEdtSafe.get();
    if (safeCounter != null && safeCounter > 0) return;

    LOG.error(
        message,
        "Current thread: " + describe(Thread.currentThread()),
        "Our dispatch thread:" + describe(ourDispatchThread),
        "SystemEventQueueThread: " + describe(getEventQueueThread()));
  }

  public void runEdtSafeAction(@NotNull Runnable runnable) {
    Integer value = ourEdtSafe.get();
    if (value == null) {
      value = Integer.valueOf(0);
    }

    ourEdtSafe.set(value + 1);

    try {
      runnable.run();
    } finally {
      int newValue = ourEdtSafe.get() - 1;
      ourEdtSafe.set(newValue >= 1 ? newValue : null);
    }
  }

  public void assertIsDispatchThread(@Nullable final JComponent component) {
    if (component == null) return;

    Thread curThread = Thread.currentThread();
    if (ourDispatchThread == curThread) {
      return;
    }

    if (Boolean.TRUE.equals(component.getClientProperty(WAS_EVER_SHOWN))) {
      assertIsDispatchThread();
    } else {
      final JRootPane root = component.getRootPane();
      if (root != null) {
        component.putClientProperty(WAS_EVER_SHOWN, Boolean.TRUE);
        assertIsDispatchThread();
      }
    }
  }

  public void assertTimeConsuming() {
    if (myTestModeFlag || myHeadlessMode || ShutDownTracker.isShutdownHookRunning()) return;
    LOG.assertTrue(
        !isDispatchThread(), "This operation is time consuming and must not be called on EDT");
  }

  public boolean tryRunReadAction(@NotNull Runnable action) {
    /**
     * if we are inside read action, do not try to acquire read lock again since it will deadlock if
     * there is a pending writeAction see {@link
     * com.intellij.util.concurrency.ReentrantWriterPreferenceReadWriteLock#allowReader()}
     */
    boolean mustAcquire = !isReadAccessAllowed();

    if (mustAcquire) {
      LOG.assertTrue(
          myTestModeFlag || !Thread.holdsLock(PsiLock.LOCK),
          "Thread must not hold PsiLock while performing readAction");
      try {
        if (!myActionsLock.readLock().attempt(0)) return false;
      } catch (InterruptedException e) {
        throw new RuntimeInterruptedException(e);
      }
    }

    try {
      action.run();
    } finally {
      if (mustAcquire) {
        myActionsLock.readLock().release();
      }
    }
    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 = Boolean.valueOf(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;
  }

  public boolean isActive() {
    if (isUnitTestMode()) return true;

    if (myActive == null) {
      Window active = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
      return active != null;
    }

    return myActive;
  }

  @Override
  public AccessToken acquireReadActionLock() {
    return acquireReadActionLockImpl(true);
  }

  private AccessToken acquireReadActionLockImpl(boolean explicit) {
    /**
     * if we are inside read action, do not try to acquire read lock again since it will deadlock if
     * there is a pending writeAction see {@link
     * com.intellij.util.concurrency.ReentrantWriterPreferenceReadWriteLock#allowReader()}
     */
    if (isReadAccessAllowed()) return AccessToken.EMPTY_ACCESS_TOKEN;

    return new ReadAccessToken(explicit);
  }

  @Override
  public AccessToken acquireWriteActionLock(Class clazz) {
    return new WriteAccessToken(clazz);
  }

  private class WriteAccessToken extends AccessToken {
    private final Class clazz;

    public WriteAccessToken(Class _clazz) {
      clazz = _clazz;
      assertCanRunWriteAction();

      ActivityTracker.getInstance().inc();
      fireBeforeWriteActionStart(_clazz);
      final AtomicBoolean stopped = new AtomicBoolean(false);

      if (ourDumpThreadsOnLongWriteActionWaiting > 0) {
        executeOnPooledThread(
            new Runnable() {
              @Override
              public void run() {
                while (!stopped.get()) {
                  try {
                    Thread.sleep(ourDumpThreadsOnLongWriteActionWaiting);
                    if (!stopped.get()) {
                      PerformanceWatcher.getInstance().dumpThreads(true);
                    }
                  } catch (InterruptedException ignored) {
                  }
                }
              }
            });
      }

      LOG.assertTrue(
          myActionsLock.isWriteLockAcquired(Thread.currentThread())
              || !Thread.holdsLock(PsiLock.LOCK),
          "Thread must not hold PsiLock while performing writeAction");
      try {
        myActionsLock.writeLock().acquire();
        acquired();
      } catch (InterruptedException e) {
        throw new RuntimeInterruptedException(e);
      }
      stopped.set(true);

      myWriteActionsStack.push(_clazz);

      fireWriteActionStarted(_clazz);
    }

    @Override
    public void finish() {
      try {
        fireWriteActionFinished(clazz);

        myWriteActionsStack.pop();
      } finally {
        myActionsLock.writeLock().release();
        released();
      }
    }
  }

  private class ReadAccessToken extends AccessToken {
    private final boolean myExplicit;

    ReadAccessToken(boolean explicit) {
      myExplicit = explicit;
      LOG.assertTrue(
          !Thread.holdsLock(PsiLock.LOCK),
          "Thread must not hold PsiLock while performing readAction");
      try {
        myActionsLock.readLock().acquire();
        if (myExplicit) acquired();
      } catch (InterruptedException e) {
        throw new RuntimeInterruptedException(e);
      }
    }

    @Override
    public void finish() {
      myActionsLock.readLock().release();
      if (myExplicit) released();
    }
  }

  public void assertWriteAccessAllowed() {
    LOG.assertTrue(
        isWriteAccessAllowed(),
        "Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())");
  }

  public boolean isWriteAccessAllowed() {
    return myActionsLock.isWriteLockAcquired(Thread.currentThread());
  }

  public void editorPaintStart() {
    myInEditorPaintCounter++;
  }

  public void editorPaintFinish() {
    myInEditorPaintCounter--;
    LOG.assertTrue(myInEditorPaintCounter >= 0);
  }

  public void addApplicationListener(@NotNull ApplicationListener l) {
    myDispatcher.addListener(l);
  }

  public void addApplicationListener(@NotNull ApplicationListener l, @NotNull Disposable parent) {
    myDispatcher.addListener(l, parent);
  }

  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 ex) {
        if (isUnitTestMode()) {
          System.out.println("Saving application settings failed");
          ex.printStackTrace();
        } else {
          LOG.info("Saving application settings failed", ex);
          invokeLater(
              new Runnable() {
                public void run() {
                  if (ex instanceof PluginException) {
                    final PluginException pluginException = (PluginException) ex;
                    PluginManager.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", ex.getLocalizedMessage()),
                        CommonBundle.getErrorTitle(),
                        Messages.getErrorIcon());
                  }
                }
              });
        }
      } finally {
        mySaveSettingsIsInProgress.set(false);
      }
    }
  }

  public void saveSettings() {
    if (myDoNotSave) return;
    _saveSettings();
  }

  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();
  }

  public void doNotSave() {
    doNotSave(true);
  }

  public void doNotSave(boolean value) {
    myDoNotSave = value;
  }

  public boolean isDoNotSave() {
    return myDoNotSave;
  }

  public <T> T[] getExtensions(final ExtensionPointName<T> extensionPointName) {
    return Extensions.getRootArea().getExtensionPoint(extensionPointName).getExtensions();
  }

  public boolean isDisposeInProgress() {
    return myDisposeInProgress;
  }

  public boolean isRestartCapable() {
    return SystemInfo.isWindows || SystemInfo.isMacOSSnowLeopard || myRestartCode > 0;
  }

  public void restart() {
    if (SystemInfo.isWindows) {
      Win32Restarter.restart();
    } else if (SystemInfo.isMacOSSnowLeopard) {
      MacRestarter.restart();
    } else if (myRestartCode > 0) {
      myExitCode = myRestartCode;
      exit(true);
    } else {
      exit(true);
    }
  }

  public boolean isSaving() {
    if (getStateStore().isSaving()) return true;
    Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
    for (Project openProject : openProjects) {
      ProjectEx project = (ProjectEx) openProject;
      if (project.getStateStore().isSaving()) return true;
    }

    return false;
  }

  @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)" : "");
  }
}
  public ApplicationImpl(
      boolean isInternal,
      boolean isUnitTestMode,
      boolean isHeadless,
      boolean isCommandLine,
      @NotNull String appName) {
    super(null);

    getPicoContainer().registerComponentInstance(Application.class, this);

    CommonBundle.assertKeyIsFound = isUnitTestMode;

    if ((isInternal || isUnitTestMode)
        && !Comparing.equal("off", System.getProperty("idea.disposer.debug"))) {
      Disposer.setDebugMode(true);
    }
    myStartTime = System.currentTimeMillis();
    myName = appName;
    ApplicationManagerEx.setApplication(this);

    PluginsFacade.INSTANCE =
        new PluginsFacade() {
          public IdeaPluginDescriptor getPlugin(PluginId id) {
            return PluginManager.getPlugin(id);
          }

          public IdeaPluginDescriptor[] getPlugins() {
            return PluginManager.getPlugins();
          }
        };

    if (!isUnitTestMode && !isHeadless) {
      Toolkit.getDefaultToolkit().getSystemEventQueue().push(IdeEventQueue.getInstance());
      if (Patches.SUN_BUG_ID_6209673) {
        RepaintManager.setCurrentManager(new IdeRepaintManager());
      }
      IconLoader.activate();
    }

    myIsInternal = isInternal;
    myTestModeFlag = isUnitTestMode;
    myHeadlessMode = isHeadless;
    myCommandLineMode = isCommandLine;

    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() {
                      final Project project = CommandLineProcessor.processExternalCommandLine(args);
                      final IdeFrame frame;
                      if (project != null) {
                        frame = WindowManager.getInstance().getIdeFrame(project);
                      } else {
                        frame = WindowManager.getInstance().getAllFrames()[0];
                      }
                      ((IdeFrameImpl) frame).requestFocus();
                    }
                  });
            }
          });
    }

    final String s = System.getProperty("jb.restart.code");
    if (s != null) {
      try {
        myRestartCode = Integer.parseInt(s);
      } catch (NumberFormatException ignore) {
      }
    }
  }
  public ApplicationImpl(
      boolean isInternal,
      boolean isUnitTestMode,
      boolean isHeadless,
      boolean isCommandLine,
      @NotNull String appName,
      Splash splash) {
    super(null);

    ApplicationManagerEx.setApplication(
        this, myLastDisposable); // reset back to null only when all components already disposed

    getPicoContainer().registerComponentInstance(Application.class, this);

    CommonBundle.assertKeyIsFound = isUnitTestMode;
    AWTExceptionHandler.register(); // do not crash AWT on exceptions
    if ((isInternal || isUnitTestMode)
        && !Comparing.equal("off", System.getProperty("idea.disposer.debug"))) {
      Disposer.setDebugMode(true);
    }
    myStartTime = System.currentTimeMillis();
    mySplash = splash;
    myName = appName;

    PluginsFacade.INSTANCE =
        new PluginsFacade() {
          public IdeaPluginDescriptor getPlugin(PluginId id) {
            return PluginManager.getPlugin(id);
          }

          public IdeaPluginDescriptor[] getPlugins() {
            return PluginManager.getPlugins();
          }
        };

    myIsInternal = isInternal;
    myTestModeFlag = isUnitTestMode;
    myHeadlessMode = isHeadless;
    myCommandLineMode = isCommandLine;

    myDoNotSave = myTestModeFlag || myHeadlessMode;

    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() {
                      final Project project = CommandLineProcessor.processExternalCommandLine(args);
                      final IdeFrame frame;
                      if (project != null) {
                        frame = WindowManager.getInstance().getIdeFrame(project);
                      } else {
                        frame = WindowManager.getInstance().getAllFrames()[0];
                      }
                      ((IdeFrameImpl) frame).requestFocus();
                    }
                  });
            }
          });
    }

    final String s = System.getProperty("jb.restart.code");
    if (s != null) {
      try {
        myRestartCode = Integer.parseInt(s);
      } catch (NumberFormatException ignore) {
      }
    }

    registerFont("/fonts/Inconsolata.ttf");
  }