blob: e9de64905415cffbb2269a0b0b7b30cdc7808bcd [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.ide;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ModalityStateListener;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BusyObject;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
public class UiActivityMonitorImpl extends UiActivityMonitor implements ModalityStateListener, Disposable {
private final FactoryMap<Project, BusyContainer> myObjects = new FactoryMap<Project, BusyContainer>() {
@Override
protected BusyContainer create(Project key) {
if (isEmpty()) {
installListener();
}
return key == null ? new BusyContainer(null) : new BusyContainer(null) {
@NotNull
@Override
protected BusyImpl createBusyImpl(@NotNull Set<UiActivity> key) {
return new BusyImpl(key, this) {
@Override
public boolean isReady() {
for (Map.Entry<Project, BusyContainer> entry : myObjects.entrySet()) {
final BusyContainer eachContainer = entry.getValue();
final BusyImpl busy = eachContainer.getOrCreateBusy(myToWatchArray);
if (busy == this) continue;
if (!busy.isOwnReady()) return false;
}
return isOwnReady();
}
};
}
};
}
};
private boolean myActive;
@NotNull
private final BusyObject myEmptyBusy = new BusyObject.Impl() {
@Override
public boolean isReady() {
return true;
}
};
public UiActivityMonitorImpl() {
}
public void installListener() {
LaterInvocator.addModalityStateListener(this, this);
}
@Override
public void dispose() {
myObjects.clear();
}
@Override
public void beforeModalityStateChanged(boolean entering) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
maybeReady();
}
});
}
public void maybeReady() {
for (BusyContainer each : myObjects.values()) {
each.onReady();
}
}
@NotNull
@Override
public BusyObject getBusy(@NotNull Project project, @NotNull UiActivity... toWatch) {
if (!isActive()) return myEmptyBusy;
return _getBusy(project, toWatch);
}
@NotNull
@Override
public BusyObject getBusy(@NotNull UiActivity... toWatch) {
if (!isActive()) return myEmptyBusy;
return _getBusy(null, toWatch);
}
@Override
public void addActivity(@NotNull final Project project, @NotNull final UiActivity activity) {
addActivity(project, activity, getDefaultModalityState());
}
@Override
public void addActivity(@NotNull final Project project,
@NotNull final UiActivity activity,
@NotNull final ModalityState effectiveModalityState) {
if (!isActive()) return;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
getBusyContainer(project).addActivity(activity, effectiveModalityState);
}
});
}
@Override
public void removeActivity(@NotNull final Project project, @NotNull final UiActivity activity) {
if (!isActive()) return;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
_getBusy(project).removeActivity(activity);
}
});
}
@Override
public void addActivity(@NotNull final UiActivity activity) {
addActivity(activity, getDefaultModalityState());
}
private static ModalityState getDefaultModalityState() {
return ApplicationManager.getApplication().getNoneModalityState();
}
@Override
public void addActivity(@NotNull final UiActivity activity, @NotNull final ModalityState effectiveModalityState) {
if (!isActive()) return;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
getBusyContainer(null).addActivity(activity, effectiveModalityState);
}
});
}
@Override
public void removeActivity(@NotNull final UiActivity activity) {
if (!isActive()) return;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
_getBusy(null).removeActivity(activity);
}
});
}
@NotNull
private BusyImpl _getBusy(@Nullable Project key, @NotNull UiActivity... toWatch) {
return getBusyContainer(key).getOrCreateBusy(toWatch);
}
@NotNull
private BusyContainer getBusyContainer(@Nullable Project key) {
BusyContainer container = myObjects.get(key);
return container != null ? container : getGlobalBusy();
}
void initBusyObjectFor(@Nullable Project key) {
myObjects.put(key, new BusyContainer(key));
}
boolean hasObjectFor(Project project) {
return myObjects.containsKey(project);
}
private BusyContainer getGlobalBusy() {
return myObjects.get(null);
}
@Override
public void clear() {
final Set<Project> keys = myObjects.keySet();
for (Project each : keys) {
myObjects.get(each).clear();
}
}
@Override
public void setActive(boolean active) {
if (myActive == active) return;
if (myActive) {
clear();
}
myActive = active;
}
public boolean isActive() {
return myActive;
}
private static class ActivityInfo {
private final ModalityState myEffectiveState;
private ActivityInfo(@NotNull ModalityState effectiveState) {
myEffectiveState = effectiveState;
}
@NotNull
public ModalityState getEffectiveState() {
return myEffectiveState;
}
}
@NotNull
protected ModalityState getCurrentState() {
return ModalityState.current();
}
private class BusyImpl extends BusyObject.Impl {
private final Map<UiActivity, ActivityInfo> myActivities = new HashMap<UiActivity, ActivityInfo>();
private final Set<UiActivity> myQueuedToRemove = new HashSet<UiActivity>();
protected final Set<UiActivity> myToWatch;
protected final UiActivity[] myToWatchArray;
private final UiActivityMonitorImpl.BusyContainer myContainer;
private BusyImpl(@NotNull Set<UiActivity> toWatch, @NotNull BusyContainer container) {
myToWatch = toWatch;
myToWatchArray = toWatch.toArray(new UiActivity[toWatch.size()]);
myContainer = container;
}
@Override
public boolean isReady() {
return isOwnReady() && getGlobalBusy().getOrCreateBusy(myToWatchArray).isOwnReady();
}
boolean isOwnReady() {
Map<UiActivity, ActivityInfo> infoToCheck = new HashMap<UiActivity, ActivityInfo>();
for (Set<UiActivity> eachActivitySet : myContainer.myActivities2Object.keySet()) {
final BusyImpl eachBusyObject = myContainer.myActivities2Object.get(eachActivitySet);
if (eachBusyObject == this) continue;
for (UiActivity eachOtherActivity : eachActivitySet) {
for (UiActivity eachToWatch : myToWatch) {
if (eachToWatch.isSameOrGeneralFor(eachOtherActivity) && eachBusyObject.myActivities.containsKey(eachOtherActivity)) {
infoToCheck.put(eachOtherActivity, eachBusyObject.myActivities.get(eachOtherActivity));
}
}
}
}
infoToCheck.putAll(myActivities);
if (infoToCheck.isEmpty()) return true;
final ModalityState current = getCurrentState();
for (Map.Entry<UiActivity, ActivityInfo> entry : infoToCheck.entrySet()) {
final ActivityInfo info = entry.getValue();
if (!current.dominates(info.getEffectiveState())) {
return false;
}
}
return true;
}
public void addActivity(@NotNull UiActivity activity, @NotNull ModalityState effectiveModalityState) {
if (!myToWatch.isEmpty() && !myToWatch.contains(activity)) return;
myActivities.put(activity, new ActivityInfo(effectiveModalityState));
myQueuedToRemove.remove(activity);
myContainer.onActivityAdded(activity);
}
public void removeActivity(@NotNull final UiActivity activity) {
if (!myActivities.containsKey(activity)) return;
myQueuedToRemove.add(activity);
Runnable runnable = new Runnable() {
@Override
public void run() {
if (!myQueuedToRemove.contains(activity)) return;
myQueuedToRemove.remove(activity);
myActivities.remove(activity);
myContainer.onActivityRemoved(BusyImpl.this, activity);
onReady();
}
};
SwingUtilities.invokeLater(runnable);
}
}
public class BusyContainer implements Disposable {
private final Map<Set<UiActivity>, BusyImpl> myActivities2Object = new HashMap<Set<UiActivity>, BusyImpl>();
private final Map<BusyImpl, Set<UiActivity>> myObject2Activities = new HashMap<BusyImpl, Set<UiActivity>>();
private final Set<UiActivity> myActivities = new HashSet<UiActivity>();
private boolean myRemovingActivityNow;
@Nullable private final Project myProject;
public BusyContainer(@Nullable Project project) {
myProject = project;
registerBusyObject(new HashSet<UiActivity>());
if (project != null) {
Disposer.register(project, this);
}
}
@NotNull
public BusyImpl getOrCreateBusy(@NotNull UiActivity... activities) {
Set<UiActivity> key = new HashSet<UiActivity>();
key.addAll(Arrays.asList(activities));
if (myActivities2Object.containsKey(key)) {
return myActivities2Object.get(key);
}
return registerBusyObject(key);
}
@NotNull
private BusyImpl registerBusyObject(@NotNull Set<UiActivity> key) {
final BusyImpl busy = createBusyImpl(key);
myActivities2Object.put(key, busy);
myObject2Activities.put(busy, key);
return busy;
}
@NotNull
protected BusyImpl createBusyImpl(@NotNull Set<UiActivity> key) {
return new BusyImpl(key, this);
}
public void onReady() {
final Iterator<Set<UiActivity>> keyIterator = myActivities2Object.keySet().iterator();
while (keyIterator.hasNext()) {
Set<UiActivity> eachKey = keyIterator.next();
final BusyImpl busy = myActivities2Object.get(eachKey);
busy.onReady();
if (busy.isReady()) {
keyIterator.remove();
myObject2Activities.remove(busy);
}
}
}
public void clear() {
final UiActivity[] activities = myActivities.toArray(new UiActivity[myActivities.size()]);
for (UiActivity each : activities) {
removeActivity(each);
}
}
public void onActivityAdded(@NotNull UiActivity activity) {
myActivities.add(activity);
}
public void onActivityRemoved(@NotNull BusyImpl busy, @NotNull UiActivity activity) {
if (myRemovingActivityNow) return;
final Map<BusyImpl, Set<UiActivity>> toRemove = new HashMap<BusyImpl, Set<UiActivity>>();
try {
myRemovingActivityNow = true;
myActivities.remove(activity);
for (BusyImpl each : myObject2Activities.keySet()) {
if (each != busy) {
each.removeActivity(activity);
}
if (each.isReady()) {
final Set<UiActivity> activities = myObject2Activities.get(busy);
toRemove.put(busy, activities);
}
}
}
finally {
for (BusyImpl each : toRemove.keySet()) {
final Set<UiActivity> activities = myObject2Activities.remove(each);
myActivities2Object.remove(activities);
}
myRemovingActivityNow = false;
}
}
public void addActivity(@NotNull UiActivity activity, @NotNull ModalityState state) {
getOrCreateBusy(activity);
final Set<BusyImpl> busies = myObject2Activities.keySet();
for (BusyImpl each : busies) {
each.addActivity(activity, state);
}
}
@Override
public void dispose() {
myObjects.remove(myProject);
}
}
}