blob: e52bb4ba5f74c7f895f28adbad0d1f77c1f5838b [file] [log] [blame]
/*
* Copyright 2000-2009 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.ui.update;
import com.intellij.ide.UiActivity;
import com.intellij.ide.UiActivityMonitor;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Map;
import java.util.TreeMap;
public class MergingUpdateQueue implements Runnable, Disposable, Activatable {
public static final JComponent ANY_COMPONENT = new JComponent() {
};
private volatile boolean myActive;
private volatile boolean mySuspended;
private final Map<Update, Update> myScheduledUpdates = new TreeMap<Update, Update>();
private final Alarm myWaiterForMerge;
private volatile boolean myFlushing;
private final String myName;
private int myMergingTimeSpan;
private JComponent myModalityStateComponent;
private final boolean myExecuteInDispatchThread;
private boolean myPassThrough;
private boolean myDisposed;
private UiNotifyConnector myUiNotifyConnector;
private boolean myRestartOnAdd;
private boolean myTrackUiActivity;
private UiActivity myUiActivity;
public MergingUpdateQueue(@NonNls @NotNull String name, int mergingTimeSpan, boolean isActive, @Nullable JComponent modalityStateComponent) {
this(name, mergingTimeSpan, isActive, modalityStateComponent, null);
}
public MergingUpdateQueue(@NonNls @NotNull String name,
int mergingTimeSpan,
boolean isActive,
@Nullable JComponent modalityStateComponent,
@Nullable Disposable parent) {
this(name, mergingTimeSpan, isActive, modalityStateComponent, parent, null);
}
public MergingUpdateQueue(@NonNls @NotNull String name,
int mergingTimeSpan,
boolean isActive,
@Nullable JComponent modalityStateComponent,
@Nullable Disposable parent,
@Nullable JComponent activationComponent) {
this(name, mergingTimeSpan, isActive, modalityStateComponent, parent, activationComponent, true);
}
public MergingUpdateQueue(@NonNls @NotNull String name,
int mergingTimeSpan,
boolean isActive,
@Nullable JComponent modalityStateComponent,
@Nullable Disposable parent,
@Nullable JComponent activationComponent,
boolean executeInDispatchThread) {
this(name, mergingTimeSpan, isActive, modalityStateComponent, parent, activationComponent,
executeInDispatchThread ? Alarm.ThreadToUse.SWING_THREAD : Alarm.ThreadToUse.POOLED_THREAD);
}
public MergingUpdateQueue(@NonNls @NotNull String name,
int mergingTimeSpan,
boolean isActive,
@Nullable JComponent modalityStateComponent,
@Nullable Disposable parent,
@Nullable JComponent activationComponent,
@NotNull Alarm.ThreadToUse thread) {
myMergingTimeSpan = mergingTimeSpan;
myModalityStateComponent = modalityStateComponent;
myName = name;
myPassThrough = ApplicationManager.getApplication().isUnitTestMode();
myExecuteInDispatchThread = thread == Alarm.ThreadToUse.SWING_THREAD;
myWaiterForMerge = createAlarm(thread, myExecuteInDispatchThread ? null : this);
if (isActive) {
showNotify();
}
if (parent != null) {
Disposer.register(parent, this);
}
if (activationComponent != null) {
setActivationComponent(activationComponent);
}
}
protected Alarm createAlarm(@NotNull Alarm.ThreadToUse thread, Disposable parent) {
return new Alarm(thread, parent);
}
public void setMergingTimeSpan(int timeSpan) {
myMergingTimeSpan = timeSpan;
if (myActive) {
restartTimer();
}
}
public void cancelAllUpdates() {
synchronized (myScheduledUpdates) {
Update[] updates = myScheduledUpdates.keySet().toArray(new Update[myScheduledUpdates.size()]);
for (Update each : updates) {
try {
each.setRejected();
}
catch (ProcessCanceledException ignored) {
}
}
myScheduledUpdates.clear();
finishActivity();
}
}
public final boolean isPassThrough() {
return myPassThrough;
}
public final void setPassThrough(boolean passThrough) {
myPassThrough = passThrough;
}
public void activate() {
showNotify();
}
public void deactivate() {
hideNotify();
}
public void suspend() {
mySuspended = true;
}
public void resume() {
mySuspended = false;
restartTimer();
}
@Override
public void hideNotify() {
if (!myActive) {
return;
}
myActive = false;
finishActivity();
clearWaiter();
}
@Override
public void showNotify() {
if (myActive) {
return;
}
myActive = true;
restartTimer();
flush();
}
public void restartTimer() {
restart(myMergingTimeSpan);
}
private void restart(final int mergingTimeSpan) {
if (!myActive) return;
clearWaiter();
if (myExecuteInDispatchThread) {
myWaiterForMerge.addRequest(this, mergingTimeSpan, getMergerModalityState());
}
else {
myWaiterForMerge.addRequest(this, mergingTimeSpan);
}
}
@Override
public void run() {
if (mySuspended) return;
flush();
}
public void flush() {
synchronized (myScheduledUpdates) {
if (myScheduledUpdates.isEmpty()) {
finishActivity();
return;
}
}
flush(true);
}
public void flush(boolean invokeLaterIfNotDispatch) {
if (myFlushing) {
return;
}
if (!isModalityStateCorrect()) {
return;
}
myFlushing = true;
final Runnable toRun = new Runnable() {
@Override
public void run() {
try {
final Update[] all;
synchronized (myScheduledUpdates) {
all = myScheduledUpdates.keySet().toArray(new Update[myScheduledUpdates.size()]);
myScheduledUpdates.clear();
}
for (Update each : all) {
each.setProcessed();
}
execute(all);
}
finally {
myFlushing = false;
if (isEmpty()) {
finishActivity();
}
}
}
};
if (myExecuteInDispatchThread && invokeLaterIfNotDispatch) {
UIUtil.invokeLaterIfNeeded(toRun);
}
else {
toRun.run();
}
}
public void setModalityStateComponent(JComponent modalityStateComponent) {
myModalityStateComponent = modalityStateComponent;
}
protected boolean isModalityStateCorrect() {
if (!myExecuteInDispatchThread) return true;
if (myModalityStateComponent == ANY_COMPONENT) return true;
ModalityState current = ApplicationManager.getApplication().getCurrentModalityState();
final ModalityState modalityState = getModalityState();
return !current.dominates(modalityState);
}
public boolean isSuspended() {
return mySuspended;
}
private static boolean isExpired(@NotNull Update each) {
return each.isDisposed() || each.isExpired();
}
protected void execute(@NotNull Update[] update) {
for (final Update each : update) {
if (isExpired(each)) {
each.setRejected();
continue;
}
if (each.executeInWriteAction()) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
execute(each);
}
});
}
else {
execute(each);
}
}
}
private void execute(@NotNull Update each) {
if (myDisposed) {
each.setRejected();
}
else {
each.run();
}
}
public void queue(@NotNull final Update update) {
if (myDisposed) return;
if (myTrackUiActivity) {
startActivity();
}
if (myPassThrough) {
update.run();
finishActivity();
return;
}
final boolean active = myActive;
synchronized (myScheduledUpdates) {
try {
if (eatThisOrOthers(update)) {
return;
}
if (active && myScheduledUpdates.isEmpty()) {
restartTimer();
}
put(update);
if (myRestartOnAdd) {
restartTimer();
}
}
finally {
if (isEmpty()) {
finishActivity();
}
}
}
}
private boolean eatThisOrOthers(@NotNull Update update) {
if (myScheduledUpdates.containsKey(update)) {
return false;
}
final Update[] updates = myScheduledUpdates.keySet().toArray(new Update[myScheduledUpdates.size()]);
for (Update eachInQueue : updates) {
if (eachInQueue.canEat(update)) {
return true;
}
if (update.canEat(eachInQueue)) {
myScheduledUpdates.remove(eachInQueue);
eachInQueue.setRejected();
}
}
return false;
}
public final void run(@NotNull Update update) {
execute(new Update[]{update});
}
private void put(@NotNull Update update) {
final Update existing = myScheduledUpdates.remove(update);
if (existing != null && existing != update) {
existing.setProcessed();
existing.setRejected();
}
myScheduledUpdates.put(update, update);
}
public boolean isActive() {
return myActive;
}
@Override
public void dispose() {
myDisposed = true;
myActive = false;
finishActivity();
clearWaiter();
}
private void clearWaiter() {
myWaiterForMerge.cancelAllRequests();
}
@SuppressWarnings({"HardCodedStringLiteral"})
public String toString() {
synchronized (myScheduledUpdates) {
return myName + " active=" + myActive + " scheduled=" + myScheduledUpdates.size();
}
}
@Nullable
private ModalityState getMergerModalityState() {
return myModalityStateComponent == ANY_COMPONENT ? null : getModalityState();
}
@NotNull
public ModalityState getModalityState() {
if (myModalityStateComponent == null) {
return ModalityState.NON_MODAL;
}
return ModalityState.stateForComponent(myModalityStateComponent);
}
public void setActivationComponent(@NotNull JComponent c) {
if (myUiNotifyConnector != null) {
Disposer.dispose(myUiNotifyConnector);
}
UiNotifyConnector connector = new UiNotifyConnector(c, this);
Disposer.register(this, connector);
myUiNotifyConnector = connector;
}
public MergingUpdateQueue setRestartTimerOnAdd(final boolean restart) {
myRestartOnAdd = restart;
return this;
}
public boolean isEmpty() {
synchronized (myScheduledUpdates) {
return myScheduledUpdates.isEmpty();
}
}
public void sendFlush() {
restart(0);
}
public boolean isFlushing() {
return myFlushing;
}
public void setTrackUiActivity(boolean trackUiActivity) {
if (myTrackUiActivity && !trackUiActivity) {
finishActivity();
}
myTrackUiActivity = trackUiActivity;
}
private void startActivity() {
if (!myTrackUiActivity) return;
UiActivityMonitor.getInstance().addActivity(getActivityId(), getModalityState());
}
private void finishActivity() {
if (!myTrackUiActivity) return;
UiActivityMonitor.getInstance().removeActivity(getActivityId());
}
@NotNull
protected UiActivity getActivityId() {
if (myUiActivity == null) {
myUiActivity = new UiActivity.AsyncBgOperation("UpdateQueue:" + myName + hashCode());
}
return myUiActivity;
}
}