/*********************************************************************************** * * Copyright (c) 2015 Kamil Baczkowicz * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * * Kamil Baczkowicz - initial API and implementation and/or initial documentation * */ package pl.baczkowicz.spy.eventbus; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class KBus implements IKBus { /** Logger. */ private static final Logger logger = LoggerFactory.getLogger(KBus.class); /** Map of subscribers to the consumer methods for that subscriber. */ private final Map<Object, Set<Consumer<?>>> subscribers = new HashMap<>(); /** Map of Consumers to filters. */ private final Map<Consumer<?>, Object> consumerFilters = new HashMap<>(); /** Map of Consumers to the class types. */ private final Map<Consumer<?>, Class<?>> consumerTypes = new HashMap<>(); /** Map of Class types to consumers. This is used for performance reasons and is recalculated after a change. */ private final Map<Class<?>, Collection<Consumer<?>>> typeConsumers = new HashMap<>(); /** Map of Class types to executors. This is used for asynchronous execution. */ private final Map<Consumer<?>, Executor> consumerExecutors = new HashMap<>(); private Collection<Consumer<?>> getConsumersForType(final Object event) { Collection<Consumer<?>> matchedConsumers; // Try to get previously matched consumers final Collection<Consumer<?>> previouslyMatchedConsumers = typeConsumers.get(event.getClass()); // If none available, try to match if (previouslyMatchedConsumers == null) { matchedConsumers = matchConsumersForType(event.getClass()); typeConsumers.put(event.getClass(), matchedConsumers); } else { matchedConsumers = previouslyMatchedConsumers; } return matchedConsumers; } private Collection<Consumer<?>> matchConsumersForType(final Class<?> eventType) { final Collection<Consumer<?>> matchedConsumers = new ArrayList<>(); logger.trace("Matching consumers for type {}", eventType); synchronized (consumerTypes) { for (final Consumer<?> consumer : consumerTypes.keySet()) { final Class<?> consumerType = consumerTypes.get(consumer); // Compares two Classes with each other (because of that couldn't use instanceof or isInstance) if (consumerType.isAssignableFrom(eventType)) { matchedConsumers.add(consumer); } } } logger.trace("Matched {} consumers for type {}: {}", matchedConsumers.size(), eventType, matchedConsumers); return matchedConsumers; } /** * Publishes an event in a synchronous way. */ @SuppressWarnings("unchecked") @Override public void publish(final Object event) { final Collection<Consumer<?>> matchedConsumers = getConsumersForType(event); for (final Consumer<?> consumer : matchedConsumers) { // No need to synchronise here, read-only final Object filter = consumerFilters.get(consumer); try { if (filter == null) { notifyConsumer((Consumer<Object>) consumer, event); } else if (event instanceof IFilterableEvent && filter.equals(((IFilterableEvent) event).getFilter())) { notifyConsumer((Consumer<Object>) consumer, event); } } catch (final ClassCastException e) { logger.warn("Consumer {} can't accept events of type = {}", consumer, event.getClass()); } } } /** * Notifies the consumer with the event. If an executor has been specified, it is used. * * @param consumer Consumer of the event * @param event The event to notify */ private void notifyConsumer(final Consumer<Object> consumer, final Object event) { final Executor executor = consumerExecutors.get(consumer); if (executor == null) { consumer.accept(event); } else { executor.execute(() -> { consumer.accept(event); }); } } private void recalculateExistingMappings() { // Recalculate all existing eventType to consumer mappings for (final Class<?> type : typeConsumers.keySet()) { typeConsumers.put(type, matchConsumersForType(type)); } } /** * {@inheritDoc} */ @Override public <S> void subscribe(final Object subscriber, final Consumer<? super S> consumer, final Class<S> eventType) { subscribe(subscriber, consumer, eventType, null, null); } /** * {@inheritDoc} */ @Override public <S> void subscribe(final Object subscriber, final Consumer<? super S> consumer, final Class<S> eventType, final Executor executor) { subscribe(subscriber, consumer, eventType, executor, null); } /** * {@inheritDoc} */ @Override public <S> void subscribeWithFilterOnly(final Object subscriber, final Consumer<? super S> consumer, final Class<S> eventType, final Object filter) { subscribe(subscriber, consumer, eventType, null, filter); } /** * {@inheritDoc} */ @Override public <S> void subscribe(final Object subscriber, final Consumer<? super S> consumer, final Class<S> eventType, final Executor executor, final Object filter) { synchronized (consumerTypes) { Set<Consumer<?>> consumers = subscribers.get(subscriber); if (consumers == null) { consumers = new HashSet<>(); subscribers.put(subscriber, consumers); } consumers.add(consumer); consumerFilters.put(consumer, filter); consumerTypes.put(consumer, eventType); consumerExecutors.put(consumer, executor); recalculateExistingMappings(); } } @Override public void unsubscribe(final Object subscriber) { synchronized (consumerTypes) { logger.trace("Trying to remove {} from subscribers", subscriber); final Set<Consumer<?>> removed = subscribers.remove(subscriber); if (removed != null) { for (final Consumer<?> consumer : removed) { consumerFilters.remove(consumer); consumerTypes.remove(consumer); } logger.trace("Removed consumers: {}", removed.size()); } else { logger.warn("Removed consumers: 0"); } recalculateExistingMappings(); } } @Override public void unsubscribeConsumer(final Object subscriber, final Consumer<?> consumer) { synchronized (consumerTypes) { logger.trace("Trying to remove {} owned by {}", consumer, subscriber); // Remove from subscriber's list of consumers final Set<Consumer<?>> consumers = subscribers.get(subscriber); if (consumers != null) { consumers.remove(consumer); } logger.trace("Removing {} from filters; contains: {}", consumer, consumerFilters.containsKey(consumer)); consumerFilters.remove(consumer); logger.trace("Removing {} from types; contains: {}", consumer, consumerTypes.containsKey(consumer)); consumerTypes.remove(consumer); recalculateExistingMappings(); } } @Override public void unsubscribeConsumer(final Object subscriber, final Class<?> eventType) { synchronized (consumerTypes) { logger.trace("Trying to remove consumer of type {} from {}", eventType, subscriber); final Collection<Consumer<?>> consumers = subscribers.get(subscriber); Consumer<?> foundConsumer = null; // Find the consumer based on its type for (final Consumer<?> consumer : consumerTypes.keySet()) { if (consumerTypes.get(consumer).equals(eventType) && consumers.contains(consumer)) { foundConsumer = consumer; break; } } if (foundConsumer != null) { unsubscribeConsumer(subscriber, foundConsumer); } } } }