blob: 33eade3c0d333ccf99747b91a594e9231b1462b6 [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.util;
import com.intellij.concurrency.JobScheduler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.util.concurrency.QueueProcessor;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.update.Activatable;
import com.intellij.util.ui.update.UiNotifyConnector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class Alarm implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.Alarm");
private volatile boolean myDisposed;
private final List<Request> myRequests = new SmartList<Request>();
private final List<Request> myPendingRequests = new SmartList<Request>();
private final ExecutorService myExecutorService;
private static final ThreadPoolExecutor ourSharedExecutorService = ConcurrencyUtil.newSingleThreadExecutor("Alarm pool(shared)", Thread.NORM_PRIORITY - 2);
private final Object LOCK = new Object();
protected final ThreadToUse myThreadToUse;
private JComponent myActivationComponent;
@Override
public void dispose() {
myDisposed = true;
cancelAllRequests();
if (myThreadToUse == ThreadToUse.POOLED_THREAD) {
myExecutorService.shutdown();
}
else if (myThreadToUse == ThreadToUse.OWN_THREAD) {
myExecutorService.shutdown();
((ThreadPoolExecutor)myExecutorService).getQueue().clear();
}
}
public void checkDisposed() {
LOG.assertTrue(!myDisposed, "Already disposed");
}
public enum ThreadToUse {
SWING_THREAD,
SHARED_THREAD,
POOLED_THREAD,
OWN_THREAD
}
/**
* Creates alarm that works in Swing thread
*/
public Alarm() {
this(ThreadToUse.SWING_THREAD);
}
public Alarm(Disposable parentDisposable) {
this(ThreadToUse.SWING_THREAD, parentDisposable);
}
public Alarm(@NotNull ThreadToUse threadToUse) {
this(threadToUse, null);
LOG.assertTrue(threadToUse != ThreadToUse.POOLED_THREAD && threadToUse != ThreadToUse.OWN_THREAD,
"You must provide parent Disposable for ThreadToUse.POOLED_THREAD and ThreadToUse.OWN_THREAD Alarm");
}
public Alarm(@NotNull ThreadToUse threadToUse, @Nullable Disposable parentDisposable) {
myThreadToUse = threadToUse;
if (threadToUse == ThreadToUse.POOLED_THREAD) {
myExecutorService = new MyExecutor();
}
else if(threadToUse == ThreadToUse.OWN_THREAD) {
myExecutorService = ConcurrencyUtil.newSingleThreadExecutor(
"Alarm pool(own)", Thread.NORM_PRIORITY - 2);
}
else {
myExecutorService = ourSharedExecutorService;
}
if (parentDisposable != null) {
Disposer.register(parentDisposable, this);
}
}
public void addRequest(@NotNull final Runnable request, final int delay, boolean runWithActiveFrameOnly) {
if (runWithActiveFrameOnly && !ApplicationManager.getApplication().isActive()) {
final MessageBus bus = ApplicationManager.getApplication().getMessageBus();
final MessageBusConnection connection = bus.connect(this);
connection.subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener.Adapter() {
@Override
public void applicationActivated(IdeFrame ideFrame) {
connection.disconnect();
addRequest(request, delay);
}
});
} else {
addRequest(request, delay);
}
}
public void addRequest(@NotNull Runnable request, long delayMillis) {
_addRequest(request, delayMillis, myThreadToUse == ThreadToUse.SWING_THREAD ? ModalityState.current() : null);
}
public void addRequest(@NotNull Runnable request, int delayMillis) {
_addRequest(request, delayMillis, myThreadToUse == ThreadToUse.SWING_THREAD ? ModalityState.current() : null);
}
public void addComponentRequest(@NotNull Runnable request, int delay) {
assert myActivationComponent != null;
_addRequest(request, delay, ModalityState.stateForComponent(myActivationComponent));
}
public void addComponentRequest(@NotNull Runnable request, long delayMillis) {
assert myActivationComponent != null;
_addRequest(request, delayMillis, ModalityState.stateForComponent(myActivationComponent));
}
public void addRequest(@NotNull Runnable request, int delayMillis, @Nullable final ModalityState modalityState) {
LOG.assertTrue(myThreadToUse == ThreadToUse.SWING_THREAD);
_addRequest(request, delayMillis, modalityState);
}
public void addRequest(@NotNull Runnable request, long delayMillis, @Nullable final ModalityState modalityState) {
LOG.assertTrue(myThreadToUse == ThreadToUse.SWING_THREAD);
_addRequest(request, delayMillis, modalityState);
}
protected void _addRequest(@NotNull Runnable request, long delayMillis, ModalityState modalityState) {
synchronized (LOCK) {
checkDisposed();
final Request requestToSchedule = new Request(request, modalityState, delayMillis);
if (myActivationComponent == null || myActivationComponent.isShowing()) {
_add(requestToSchedule);
}
else {
if (!myPendingRequests.contains(requestToSchedule)) {
myPendingRequests.add(requestToSchedule);
}
}
}
}
private void _add(@NotNull Request requestToSchedule) {
final ScheduledFuture<?> future = JobScheduler.getScheduler().schedule(requestToSchedule, requestToSchedule.myDelay, TimeUnit.MILLISECONDS);
requestToSchedule.setFuture(future);
myRequests.add(requestToSchedule);
}
private void flushPending() {
for (Request each : myPendingRequests) {
_add(each);
}
myPendingRequests.clear();
}
public boolean cancelRequest(@NotNull Runnable request) {
synchronized (LOCK) {
cancelRequest(request, myRequests);
cancelRequest(request, myPendingRequests);
return true;
}
}
private void cancelRequest(@NotNull Runnable request, @NotNull List<Request> list) {
for (int i = list.size()-1; i>=0; i--) {
Request r = list.get(i);
if (r.getTask() == request) {
r.cancel();
list.remove(i);
}
}
}
public int cancelAllRequests() {
synchronized (LOCK) {
int count = cancelAllRequests(myRequests);
cancelAllRequests(myPendingRequests);
return count;
}
}
private int cancelAllRequests(@NotNull List<Request> list) {
int count = 0;
for (Request request : list) {
count++;
request.cancel();
}
list.clear();
return count;
}
public void flush() {
List<Pair<Request, Runnable>> requests;
synchronized (LOCK) {
if (myRequests.isEmpty()) {
return;
}
requests = new SmartList<Pair<Request, Runnable>>();
for (Request request : myRequests) {
Runnable existingTask = request.cancel();
if (existingTask != null) {
requests.add(Pair.create(request, existingTask));
}
}
myRequests.clear();
}
for (Pair<Request, Runnable> request : requests) {
synchronized (LOCK) {
request.first.myTask = request.second;
}
request.first.run();
}
}
public int getActiveRequestCount() {
synchronized (LOCK) {
return myRequests.size();
}
}
public boolean isEmpty() {
synchronized (LOCK) {
return myRequests.isEmpty();
}
}
protected boolean isEdt() {
return isEventDispatchThread();
}
public static boolean isEventDispatchThread() {
final Application app = ApplicationManager.getApplication();
return app != null && app.isDispatchThread() || EventQueue.isDispatchThread();
}
private class Request implements Runnable {
private Runnable myTask; // guarded by LOCK
private final ModalityState myModalityState;
private Future<?> myFuture; // guarded by LOCK
private final long myDelay;
private Request(@NotNull final Runnable task, @Nullable ModalityState modalityState, long delayMillis) {
synchronized (LOCK) {
myTask = task;
myModalityState = modalityState;
myDelay = delayMillis;
}
}
@Override
public void run() {
try {
if (myDisposed) {
return;
}
synchronized (LOCK) {
if (myTask == null) {
return;
}
}
final Runnable scheduledTask = new Runnable() {
@Override
public void run() {
final Runnable task;
synchronized (LOCK) {
task = myTask;
if (task == null) return;
myTask = null;
myRequests.remove(Request.this);
myFuture = null;
}
if (myThreadToUse == ThreadToUse.SWING_THREAD && !isEdt()) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!myDisposed) {
QueueProcessor.runSafely(task);
}
}
});
}
else {
QueueProcessor.runSafely(task);
}
}
@Override
public String toString() {
return "ScheduledTask "+Request.this;
}
};
if (myModalityState == null) {
Future<?> future = myExecutorService.submit(scheduledTask);
synchronized (LOCK) {
myFuture = future;
}
}
else {
final Application app = ApplicationManager.getApplication();
if (app == null) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(scheduledTask);
}
else if (app.isDispatchThread() && app.getCurrentModalityState().equals(myModalityState)) {
scheduledTask.run();
}
else {
app.invokeLater(scheduledTask, myModalityState);
}
}
}
catch (Throwable e) {
LOG.error(e);
}
}
private Runnable getTask() {
synchronized (LOCK) {
return myTask;
}
}
public void setFuture(@NotNull ScheduledFuture<?> future) {
synchronized (LOCK) {
myFuture = future;
}
}
public ModalityState getModalityState() {
return myModalityState;
}
/**
* @return task if not yet executed
*/
@Nullable
private Runnable cancel() {
synchronized (LOCK) {
if (myFuture != null) {
myFuture.cancel(false);
// TODO Use java.util.concurrent.ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true) when on jdk 1.7
((ScheduledThreadPoolExecutor)JobScheduler.getScheduler()).remove((Runnable)myFuture);
myFuture = null;
}
Runnable task = myTask;
myTask = null;
return task;
}
}
@Override
public String toString() {
Runnable task = getTask();
return super.toString() + (task != null ? ": "+task : "");
}
}
public Alarm setActivationComponent(@NotNull final JComponent component) {
myActivationComponent = component;
new UiNotifyConnector(component, new Activatable() {
@Override
public void showNotify() {
flushPending();
}
@Override
public void hideNotify() {
}
});
return this;
}
public boolean isDisposed() {
return myDisposed;
}
private class MyExecutor extends AbstractExecutorService {
private final AtomicBoolean isShuttingDown = new AtomicBoolean();
private final QueueProcessor<Runnable> myProcessor = QueueProcessor.createRunnableQueueProcessor();
@Override
public void shutdown() {
myProcessor.clear();
isShuttingDown.set(myDisposed);
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
throw new UnsupportedOperationException();
}
@Override
public boolean isShutdown() {
return isShuttingDown.get();
}
@Override
public boolean isTerminated() {
return isShutdown() && myProcessor.isEmpty();
}
@Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public void execute(@NotNull Runnable command) {
myProcessor.add(command);
}
}
}