blob: f53b920dea488f4ec071e12fdcdafc79a4a75dd6 [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.ide.IdeEventQueue;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ModalityStateListener;
import com.intellij.openapi.diagnostic.FrequentEventDetector;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.util.ArrayUtil;
import com.intellij.util.EventDispatcher;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.*;
import com.intellij.util.containers.Stack;
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.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@SuppressWarnings({"SSBasedInspection"})
public class LaterInvocator {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.application.impl.LaterInvocator");
private static final boolean DEBUG = LOG.isDebugEnabled();
public static final Object LOCK = new Object(); //public for tests
private static final IdeEventQueue ourEventQueue = IdeEventQueue.getInstance();
private static final FrequentEventDetector ourFrequentEventDetector = new FrequentEventDetector(1009, 100);
private LaterInvocator() {
}
private static class RunnableInfo {
@NotNull private final Runnable runnable;
@NotNull private final ModalityState modalityState;
@NotNull private final Condition<Object> expired;
@NotNull private final ActionCallback callback;
public RunnableInfo(@NotNull Runnable runnable,
@NotNull ModalityState modalityState,
@NotNull Condition<Object> expired,
@NotNull ActionCallback callback) {
this.runnable = runnable;
this.modalityState = modalityState;
this.expired = expired;
this.callback = callback;
}
@NonNls
public String toString() {
return "[runnable: " + runnable + "; state=" + modalityState + (expired.value(null) ? "; expired" : "")+"] ";
}
}
private static final List<Object> ourModalEntities = ContainerUtil.createLockFreeCopyOnWriteList();
private static final List<RunnableInfo> ourQueue = new ArrayList<RunnableInfo>(); //protected by LOCK
private static volatile int ourQueueSkipCount = 0; // optimization
private static final Runnable ourFlushQueueRunnable = new FlushQueue();
private static final Stack<AWTEvent> ourEventStack = new Stack<AWTEvent>(); // guarded by RUN_LOCK
private static final EventDispatcher<ModalityStateListener> ourModalityStateMulticaster =
EventDispatcher.create(ModalityStateListener.class);
private static final List<RunnableInfo> ourForcedFlushQueue = new ArrayList<RunnableInfo>();
public static void addModalityStateListener(@NotNull ModalityStateListener listener, @NotNull Disposable parentDisposable) {
ourModalityStateMulticaster.addListener(listener, parentDisposable);
}
@NotNull
static ModalityStateEx modalityStateForWindow(@NotNull Window window) {
int index = ourModalEntities.indexOf(window);
if (index < 0) {
Window owner = window.getOwner();
if (owner == null) return (ModalityStateEx)ApplicationManager.getApplication().getNoneModalityState();
ModalityStateEx ownerState = modalityStateForWindow(owner);
if (window instanceof Dialog && ((Dialog)window).isModal()) {
return ownerState.appendEntity(window);
}
return ownerState;
}
ArrayList<Object> result = new ArrayList<Object>();
for (Object entity : ourModalEntities) {
if (entity instanceof Window) {
result.add(entity);
}
else if (entity instanceof ProgressIndicator) {
if (((ProgressIndicator)entity).isModal()) {
result.add(entity);
}
}
}
return new ModalityStateEx(result.toArray());
}
@NotNull
public static ActionCallback invokeLater(@NotNull Runnable runnable) {
return invokeLater(runnable, Conditions.FALSE);
}
@NotNull
public static ActionCallback invokeLater(@NotNull Runnable runnable, @NotNull Condition expired) {
ModalityState modalityState = ModalityState.defaultModalityState();
return invokeLater(runnable, modalityState, expired);
}
@NotNull
public static ActionCallback invokeLater(@NotNull Runnable runnable, @NotNull ModalityState modalityState) {
return invokeLater(runnable, modalityState, Conditions.FALSE);
}
@NotNull
public static ActionCallback invokeLater(@NotNull Runnable runnable,
@NotNull ModalityState modalityState,
@NotNull Condition<Object> expired) {
ourFrequentEventDetector.eventHappened();
final ActionCallback callback = new ActionCallback();
RunnableInfo runnableInfo = new RunnableInfo(runnable, modalityState, expired, callback);
synchronized (LOCK) {
ourQueue.add(runnableInfo);
}
requestFlush();
return callback;
}
public static void invokeAndWait(@NotNull final Runnable runnable, @NotNull ModalityState modalityState) {
LOG.assertTrue(!isDispatchThread());
final Semaphore semaphore = new Semaphore();
semaphore.down();
Runnable runnable1 = new Runnable() {
@Override
public void run() {
try {
runnable.run();
}
finally {
semaphore.up();
}
}
@NonNls
public String toString() {
return "InvokeAndWait[" + runnable + "]";
}
};
invokeLater(runnable1, modalityState);
semaphore.waitFor();
}
public static void enterModal(@NotNull Object modalEntity) {
LOG.assertTrue(isDispatchThread(), "enterModal() should be invoked in event-dispatch thread");
if (LOG.isDebugEnabled()) {
LOG.debug("enterModal:" + modalEntity);
}
ourModalityStateMulticaster.getMulticaster().beforeModalityStateChanged(true);
ourModalEntities.add(modalEntity);
}
public static void leaveModal(@NotNull Object modalEntity) {
LOG.assertTrue(isDispatchThread(), "leaveModal() should be invoked in event-dispatch thread");
if (LOG.isDebugEnabled()) {
LOG.debug("leaveModal:" + modalEntity);
}
ourModalityStateMulticaster.getMulticaster().beforeModalityStateChanged(false);
boolean removed = ourModalEntities.remove(modalEntity);
LOG.assertTrue(removed, modalEntity);
cleanupQueueForModal(modalEntity);
ourQueueSkipCount = 0;
requestFlush();
}
private static void cleanupQueueForModal(@NotNull final Object modalEntity) {
synchronized (LOCK) {
for (Iterator<RunnableInfo> iterator = ourQueue.iterator(); iterator.hasNext(); ) {
RunnableInfo runnableInfo = iterator.next();
if (runnableInfo.modalityState instanceof ModalityStateEx) {
ModalityStateEx stateEx = (ModalityStateEx)runnableInfo.modalityState;
if (stateEx.contains(modalEntity)) {
ourForcedFlushQueue.add(runnableInfo);
iterator.remove();
}
}
}
}
}
@TestOnly
static void leaveAllModals() {
ourModalEntities.clear();
ourQueueSkipCount = 0;
requestFlush();
}
@NotNull
public static Object[] getCurrentModalEntities() {
ApplicationManager.getApplication().assertIsDispatchThread();
//TODO!
//LOG.assertTrue(IdeEventQueue.getInstance().isInInputEvent() || isInMyRunnable());
return ArrayUtil.toObjectArray(ourModalEntities);
}
public static boolean isInModalContext() {
LOG.assertTrue(isDispatchThread());
return !ourModalEntities.isEmpty();
}
private static boolean isDispatchThread() {
return ApplicationManager.getApplication().isDispatchThread();
}
private static void requestFlush() {
if (FLUSHER_SCHEDULED.compareAndSet(false, true)) {
SwingUtilities.invokeLater(ourFlushQueueRunnable);
}
}
@Nullable
private static RunnableInfo pollNext() {
synchronized (LOCK) {
if (!ourForcedFlushQueue.isEmpty()) {
final RunnableInfo toRun = ourForcedFlushQueue.remove(0);
if (!toRun.expired.value(null)) {
return toRun;
}
else {
toRun.callback.setDone();
}
}
ModalityState currentModality;
if (ourModalEntities.isEmpty()) {
Application application = ApplicationManager.getApplication();
currentModality = application == null ? ModalityState.NON_MODAL : application.getNoneModalityState();
}
else {
currentModality = new ModalityStateEx(ourModalEntities.toArray());
}
while (ourQueueSkipCount < ourQueue.size()) {
RunnableInfo info = ourQueue.get(ourQueueSkipCount);
if (info.expired.value(null)) {
ourQueue.remove(ourQueueSkipCount);
info.callback.setDone();
continue;
}
if (!currentModality.dominates(info.modalityState)) {
ourQueue.remove(ourQueueSkipCount);
return info;
}
ourQueueSkipCount++;
}
return null;
}
}
private static final AtomicBoolean FLUSHER_SCHEDULED = new AtomicBoolean(false);
private static final Object RUN_LOCK = new Object();
private static class FlushQueue implements Runnable {
private RunnableInfo myLastInfo;
@Override
public void run() {
FLUSHER_SCHEDULED.set(false);
final RunnableInfo lastInfo = pollNext();
myLastInfo = lastInfo;
if (lastInfo != null) {
synchronized (RUN_LOCK) { // necessary only because of switching to our own event queue
AWTEvent event = ourEventQueue.getTrueCurrentEvent();
ourEventStack.push(event);
int stackSize = ourEventStack.size();
try {
lastInfo.runnable.run();
lastInfo.callback.setDone();
}
catch (ProcessCanceledException ex) {
// ignore
}
catch (Throwable t) {
if (t instanceof StackOverflowError) {
t.printStackTrace();
}
LOG.error(t);
}
finally {
LOG.assertTrue(ourEventStack.size() == stackSize);
ourEventStack.pop();
if (!DEBUG) myLastInfo = null;
}
}
requestFlush();
}
}
@NonNls
public String toString() {
return "LaterInvocator.FlushQueue" + (myLastInfo == null ? "" : " lastInfo="+myLastInfo);
}
}
@TestOnly
static String dumpQueue() {
synchronized (LOCK) {
@NonNls String result = "";
if (!ourForcedFlushQueue.isEmpty()) {
result = "(Forced queue: " + ourForcedFlushQueue + ") ";
}
List<RunnableInfo> r = new ArrayList<RunnableInfo>(ourQueue);
result += r + (ourQueueSkipCount == 0 ? "" : " (ourQueueSkipCount="+ourQueueSkipCount+")")
+ (ourModalEntities.isEmpty() ? " (non-modal)" : " (modal entities: "+ourModalEntities+")"
+ (FLUSHER_SCHEDULED.get() ? " (Flusher scheduled)" : "")
);
return result;
}
}
}