All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.pustike.eventbus.SubscriberRegistry Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016-2018 the original author or authors.
 *
 * 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
 *
 * https://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 io.github.pustike.eventbus;

import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Registry of subscribers to a single event bus.
 * @author Colin Decker
 */
final class SubscriberRegistry {
    /**
     * All registered subscribers, indexed by event type.
     *
     * 

The {@link CopyOnWriteArraySet} values make it easy and relatively lightweight to get an immutable snapshot * of all current subscribers to an event without any locking. */ private final ConcurrentMap> subscribers; /** * The event bus this registry belongs to. */ private final EventBus bus; /** * The cache for subscriberMethods and eventTypeHierarchy. */ private final SubscriberLoader subscriberLoader; SubscriberRegistry(EventBus bus) { this(bus, null); } SubscriberRegistry(EventBus bus, SubscriberLoader subscriberLoader) { this.bus = Objects.requireNonNull(bus); this.subscribers = new ConcurrentHashMap<>(); this.subscriberLoader = subscriberLoader == null ? new DefaultSubscriberLoader() : subscriberLoader; } /** * Registers all subscriber methods on the given listener object. */ void register(Object listener) { Map> listenerMethods = findAllSubscribers(listener); for (Map.Entry> entry : listenerMethods.entrySet()) { int hashCode = entry.getKey(); Collection eventMethodsInListener = entry.getValue(); CopyOnWriteArraySet eventSubscribers = subscribers.get(hashCode); if (eventSubscribers == null) { CopyOnWriteArraySet newSet = new CopyOnWriteArraySet<>(); eventSubscribers = firstNonNull(subscribers.putIfAbsent(hashCode, newSet), newSet); } eventSubscribers.addAll(eventMethodsInListener); } } /** * Unregisters all subscribers on the given listener object. */ void unregister(Object listener) { if (listener instanceof Subscriber) { Subscriber subscriber = (Subscriber) listener; CopyOnWriteArraySet currentSubscribers = subscribers.get(subscriber.registryKey); if (currentSubscribers != null) { currentSubscribers.remove(subscriber); } } else { Map> listenerMethods = findAllSubscribers(listener); for (Map.Entry> entry : listenerMethods.entrySet()) { int hashCode = entry.getKey(); Collection listenerMethodsForType = entry.getValue(); CopyOnWriteArraySet currentSubscribers = subscribers.get(hashCode); if (currentSubscribers != null) { currentSubscribers.removeAll(listenerMethodsForType); } // don't try to remove the set if it's empty; that can't be done safely without a lock // anyway, if the set is empty it'll just be wrapping an array of length 0 } } } /** * Gets an iterator representing an immutable snapshot of all subscribers to the given event at the time this method * is called. */ Iterator getSubscribers(Object event) { if (event instanceof TypeSupplier) { Class eventSourceType = ((TypeSupplier) event).getType(); int hashCode = Objects.hash(event.getClass().getName(), eventSourceType.getName()); CopyOnWriteArraySet eventSubscribers = subscribers.get(hashCode); return eventSubscribers != null ? eventSubscribers.iterator() : Collections.emptyIterator(); } else { Set> eventTypes = subscriberLoader.flattenHierarchy(event.getClass()); LinkedList> subscriberIterators = new LinkedList<>(); for (Class eventType : eventTypes) { int hashCode = eventType.getName().hashCode(); CopyOnWriteArraySet eventSubscribers = subscribers.get(hashCode); if (eventSubscribers != null) {// eager no-copy snapshot subscriberIterators.add(eventSubscribers.iterator()); } } return new IteratorAggregator<>(subscriberIterators); } } /** * Returns all subscribers for the given listener grouped by the type of event they subscribe to. */ private Map> findAllSubscribers(Object listener) { Map> methodsInListener = new HashMap<>(); WeakReference weakListener = new WeakReference<>(listener); Class clazz = listener.getClass(); for (Method method : subscriberLoader.findSubscriberMethods(clazz)) { int hashCode = computeParameterHashCode(method); List subscriberList = methodsInListener.computeIfAbsent(hashCode, k -> new ArrayList<>()); subscriberList.add(Subscriber.create(bus, weakListener, method, hashCode)); } return methodsInListener; } private int computeParameterHashCode(Method method) { Class parameterClass = method.getParameterTypes()[0]; Type parameterType = method.getGenericParameterTypes()[0]; if (parameterClass.equals(TypedEvent.class) && parameterType instanceof ParameterizedType) { ParameterizedType firstParam = (ParameterizedType) parameterType; Type[] typeArguments = firstParam.getActualTypeArguments(); return Objects.hash(firstParam.getRawType().getTypeName(), typeArguments[0].getTypeName()); } return parameterClass.getName().hashCode(); } private static T firstNonNull(T first, T second) { return first != null ? first : Objects.requireNonNull(second); } /** * Clear all subscribers from the cache. */ void clear() { subscribers.clear(); subscriberLoader.invalidateAll(); } Set getSubscribersForTesting(Class eventType) { int hashCode = eventType.getName().hashCode(); /*if (event instanceof TypeSupplier) { Class eventSourceType = ((TypeSupplier) event).getType(); hashCode = (31 + hashCode) * 31 + eventSourceType.getName().hashCode(); }*/ CopyOnWriteArraySet eventSubscribers = subscribers.get(hashCode); return eventSubscribers != null ? eventSubscribers : Collections.emptySet(); } private static final class IteratorAggregator implements Iterator { private final LinkedList> internalIterators; private Iterator currentIterator = null; private IteratorAggregator(List> iterators) { internalIterators = new LinkedList<>(iterators); } @Override public boolean hasNext() { return (currentIterator != null && currentIterator.hasNext()) || (!internalIterators.isEmpty() && internalIterators.getFirst().hasNext()); } @Override public E next() { if (currentIterator != null && currentIterator.hasNext()) { return currentIterator.next(); } currentIterator = internalIterators.pollFirst(); if (currentIterator != null) { return currentIterator.next(); } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy