/* * Copyright 2000-2012 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. */ /* * @author max */ package com.intellij.util.messages.impl; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.Topic; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Iterator; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; public class MessageBusImpl implements MessageBus { private static final Logger LOG = Logger.getInstance("#com.intellij.util.messages.impl.MessageBusImpl"); private final ThreadLocal<Queue<DeliveryJob>> myMessageQueue = createThreadLocalQueue(); private final ConcurrentMap<Topic, Object> mySyncPublishers = new ConcurrentHashMap<Topic, Object>(); private final ConcurrentMap<Topic, Object> myAsyncPublishers = new ConcurrentHashMap<Topic, Object>(); private final ConcurrentMap<Topic, List<MessageBusConnectionImpl>> mySubscribers = new ConcurrentHashMap<Topic, List<MessageBusConnectionImpl>>(); private final List<MessageBusImpl> myChildBuses = ContainerUtil.createLockFreeCopyOnWriteList(); private static final Object NA = new Object(); private MessageBusImpl myParentBus; //is used for debugging purposes @SuppressWarnings({"UnusedDeclaration", "FieldCanBeLocal"}) private final Object myOwner; private boolean myDisposed; @SuppressWarnings("UnusedDeclaration") public MessageBusImpl() { this("?", null); } public MessageBusImpl(@NotNull Object owner, MessageBus parentBus) { myOwner = owner.toString(); myParentBus = (MessageBusImpl)parentBus; if (myParentBus != null) { myParentBus.notifyChildBusCreated(this); LOG.assertTrue(myParentBus.myChildBuses.contains(this)); } } @Override public MessageBus getParent() { return myParentBus; } private void notifyChildBusCreated(final MessageBusImpl childBus) { myChildBuses.add(childBus); LOG.assertTrue(childBus.myParentBus == this); } private void notifyChildBusDisposed(final MessageBusImpl childBus) { boolean removed = myChildBuses.remove(childBus); LOG.assertTrue(removed); } private static class DeliveryJob { public DeliveryJob(final MessageBusConnectionImpl connection, final Message message) { this.connection = connection; this.message = message; } public final MessageBusConnectionImpl connection; public final Message message; @NonNls @Override public String toString() { return "{ DJob connection:" + connection.toString() + "; message: " + message + " }"; } } @Override @NotNull public MessageBusConnection connect() { checkNotDisposed(); return new MessageBusConnectionImpl(this); } @Override @NotNull public MessageBusConnection connect(@NotNull Disposable parentDisposable) { final MessageBusConnection connection = connect(); Disposer.register(parentDisposable, connection); return connection; } @Override @NotNull @SuppressWarnings({"unchecked"}) public <L> L syncPublisher(@NotNull final Topic<L> topic) { checkNotDisposed(); L publisher = (L)mySyncPublishers.get(topic); if (publisher == null) { final Class<L> listenerClass = topic.getListenerClass(); InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { sendMessage(new Message(topic, method, args)); return NA; } }; publisher = (L)Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, handler); publisher = (L)ConcurrencyUtil.cacheOrGet(mySyncPublishers, topic, publisher); } return publisher; } @Override @NotNull @SuppressWarnings({"unchecked"}) public <L> L asyncPublisher(@NotNull final Topic<L> topic) { checkNotDisposed(); L publisher = (L)myAsyncPublishers.get(topic); if (publisher == null) { final Class<L> listenerClass = topic.getListenerClass(); InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { postMessage(new Message(topic, method, args)); return NA; } }; publisher = (L)Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[]{listenerClass}, handler); publisher = (L)ConcurrencyUtil.cacheOrGet(myAsyncPublishers, topic, publisher); } return publisher; } @Override public void dispose() { checkNotDisposed(); Queue<DeliveryJob> jobs = myMessageQueue.get(); if (!jobs.isEmpty()) { LOG.error("Not delivered events in the queue: " + jobs); } myMessageQueue.remove(); if (myParentBus != null) { myParentBus.notifyChildBusDisposed(this); myParentBus = null; } myDisposed = true; } private void checkNotDisposed() { LOG.assertTrue(!myDisposed, "Already disposed"); } private void postMessage(Message message) { checkNotDisposed(); final Topic topic = message.getTopic(); final List<MessageBusConnectionImpl> topicSubscribers = mySubscribers.get(topic); if (topicSubscribers != null) { Queue<DeliveryJob> queue = myMessageQueue.get(); for (MessageBusConnectionImpl subscriber : topicSubscribers) { queue.offer(new DeliveryJob(subscriber, message)); subscriber.scheduleMessageDelivery(message); } } Topic.BroadcastDirection direction = topic.getBroadcastDirection(); if (direction == Topic.BroadcastDirection.TO_CHILDREN) { for (MessageBusImpl childBus : myChildBuses) { childBus.postMessage(message); } } if (direction == Topic.BroadcastDirection.TO_PARENT && myParentBus != null) { myParentBus.postMessage(message); } } private void sendMessage(Message message) { pumpMessages(); postMessage(message); pumpMessages(); } private void pumpMessages() { checkNotDisposed(); if (myParentBus != null) { LOG.assertTrue(myParentBus.myChildBuses.contains(this)); myParentBus.pumpMessages(); } else { doPumpMessages(); } } private void doPumpMessages() { Queue<DeliveryJob> queue = myMessageQueue.get(); do { DeliveryJob job = queue.poll(); if (job == null) break; job.connection.deliverMessage(job.message); } while (true); for (MessageBusImpl childBus : myChildBuses) { LOG.assertTrue(childBus.myParentBus == this); childBus.doPumpMessages(); } } void notifyOnSubscription(final MessageBusConnectionImpl connection, final Topic topic) { checkNotDisposed(); List<MessageBusConnectionImpl> topicSubscribers = mySubscribers.get(topic); if (topicSubscribers == null) { topicSubscribers = ContainerUtil.createLockFreeCopyOnWriteList(); topicSubscribers = ConcurrencyUtil.cacheOrGet(mySubscribers, topic, topicSubscribers); } topicSubscribers.add(connection); } void notifyConnectionTerminated(final MessageBusConnectionImpl connection) { for (List<MessageBusConnectionImpl> topicSubscribers : mySubscribers.values()) { topicSubscribers.remove(connection); } if (myDisposed) return; final Iterator<DeliveryJob> i = myMessageQueue.get().iterator(); while (i.hasNext()) { final DeliveryJob job = i.next(); if (job.connection == connection) { i.remove(); } } } void deliverSingleMessage() { checkNotDisposed(); final DeliveryJob job = myMessageQueue.get().poll(); if (job == null) return; job.connection.deliverMessage(job.message); } @NotNull static <T> ThreadLocal<Queue<T>> createThreadLocalQueue() { return new ThreadLocal<Queue<T>>() { @Override protected Queue<T> initialValue() { return new ConcurrentLinkedQueue<T>(); } }; } }