blob: 2b09896b48e23034cd4483ed67fcd419a6eaef05 [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;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
public class PendingEventDispatcher <T extends EventListener> {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.PendingEventDispatcher");
private final T myMulticaster;
private final List<T> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final Map<T, Boolean> myListenersState = new HashMap<T, Boolean>();
private final Stack<T> myDispatchingListeners = new Stack<T>();
private Method myCurrentDispatchMethod = null;
private Object[] myCurrentDispatchArgs = null;
private static int ourDispatchingEventsCounter = 0;
private final boolean myAssertDispatchThread;
public static <T extends EventListener> PendingEventDispatcher<T> create(Class<T> listenerClass) {
return create(listenerClass, true);
}
public static <T extends EventListener> PendingEventDispatcher<T> create(Class<T> listenerClass, boolean assertDispatchThread) {
return new PendingEventDispatcher<T>(listenerClass, assertDispatchThread);
}
public static boolean isDispatchingAnyEvent(){
return ourDispatchingEventsCounter > 0;
}
public boolean isDispatching(){
return myCurrentDispatchMethod != null;
}
private PendingEventDispatcher(Class<T> listenerClass, boolean assertDispatchThread) {
myAssertDispatchThread = assertDispatchThread;
InvocationHandler handler = new InvocationHandler() {
@NonNls public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
@NonNls String methodName = method.getName();
if (methodName.equals("toString")) {
return "Multicaster";
}
else if (methodName.equals("hashCode")) {
return Integer.valueOf(System.identityHashCode(proxy));
}
else if (methodName.equals("equals")) {
return proxy == args[0] ? Boolean.TRUE : Boolean.FALSE;
}
else {
LOG.error("Incorrect Object's method invoked for proxy:" + methodName);
return null;
}
}
else {
dispatch(method, args);
return null;
}
}
};
myMulticaster = (T)Proxy.newProxyInstance(listenerClass.getClassLoader(),
new Class[]{listenerClass},
handler
);
}
public boolean hasListeners() {
return !myListeners.isEmpty();
}
public T getMulticaster() {
return myMulticaster;
}
public synchronized void addListener(T listener) {
//LOG.assertTrue(!myListeners.containsKey(listener), "Cannot add the same listener twice");
myListeners.add(listener);
myListenersState.put(listener, Boolean.TRUE);
}
public synchronized void addListener(final T listener, Disposable parentDisposable) {
addListener(listener);
Disposer.register(parentDisposable, new Disposable() {
public void dispose() {
removeListener(listener);
}
});
}
public synchronized void removeListener(T listener) {
//LOG.assertTrue(myListeners.containsKey(listener), "Attempt to remove a non-registered listener");
myListeners.remove(listener);
myListenersState.remove(listener);
}
public void dispatchPendingEvent(final T listener) {
Boolean dispatched = myListenersState.get(listener);
//if (!LOG.assertTrue(dispatched != null, "dispathPendingEvents() should not be invoked for listener which was not registered")) return;
if (dispatched == null) return;
if (!dispatched.booleanValue()) {
Application application = ApplicationManager.getApplication();
if (application.isDispatchThread()) {
invoke(listener);
}
else {
// We do not dispatch events in non swing thread. A potential deadlock otherwise.
return;
/*
ModalityState state = ProgressManager.getInstance().getProgressIndicator().getModalityState();
application.invokeAndWait(new Runnable() {
public void run() {
invoke(listener);
}
}, state);
*/
}
}
/* Seems like this assertion is incorrect and there's nothing wrong with just skipping in this situation.
else {
if (myDispatchingListeners.size() > 0 && listener != myDispatchingListeners.peek() && myDispatchingListeners.contains(listener)){
LOG.error("Cyclic dispatching is prohibited");
}
}
*/
}
private void assertDispatchThread() {
Application application = ApplicationManager.getApplication();
if (myAssertDispatchThread && !application.isUnitTestMode()) {
application.assertIsDispatchThread();
}
}
private void dispatch(Method method, Object[] args) {
assertDispatchThread();
if(myCurrentDispatchMethod != null) {
LOG.error("Event cannot be raised when dispatching another event is in progress. Dispatching " + myCurrentDispatchMethod.getName());
}
method.setAccessible(true);
ourDispatchingEventsCounter++;
myCurrentDispatchMethod = method;
myCurrentDispatchArgs = args;
try {
List<T> listeners = getListeners();
for (T listener : listeners) {
myListenersState.put(listener, Boolean.FALSE);
}
for (T listener : listeners) {
invoke(listener);
}
}
finally {
ourDispatchingEventsCounter--;
myCurrentDispatchMethod = null;
myCurrentDispatchArgs = null;
}
}
private void invoke(T listener) {
Boolean state = myListenersState.get(listener);
if (state == null/*removed*/ || state.booleanValue()) {
return;
}
myListenersState.put(listener, Boolean.TRUE);
try {
myDispatchingListeners.push(listener);
myCurrentDispatchMethod.invoke(listener, myCurrentDispatchArgs);
}
catch(AbstractMethodError e) {
//Do nothing. This listener just does not implement something newly added yet.
}
catch (InvocationTargetException e) {
LOG.error(e.getCause());
}
catch (IllegalAccessException e) {
LOG.error(e.getCause());
}
finally {
myDispatchingListeners.pop();
}
}
public List<T> getListeners() {
return myListeners;
}
}