| /* |
| * 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; |
| } |
| } |
| } |