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

org.rx.core.EventBus Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package org.rx.core;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.rx.annotation.Metadata;
import org.rx.annotation.Subscribe;
import org.rx.bean.Tuple;
import org.rx.exception.InvalidException;
import org.rx.exception.TraceHandler;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@Slf4j
public class EventBus implements EventPublisher {
    public static final EventBus DEFAULT = new EventBus();
    static final int TOPIC_MAP_INITIAL_CAPACITY = 4;

    static  Serializable getTopic(T event) {
        Serializable topic = null;
        Metadata m = event.getClass().getAnnotation(Metadata.class);
        if (m != null) {
            if (m.topicClass() != Object.class) {
                topic = m.topicClass();
            } else if (!m.topic().isEmpty()) {
                topic = m.topic();
            }
        }
        return topic;
    }

    public final Delegate> onDeadEvent = Delegate.create();
    //eventType -> topic -> eventMethodsInListener
    final Map, Map>>> subscribers = new ConcurrentHashMap<>();

    public  void register(@NonNull T subscriber) {
        for (Map.Entry, Set>> entry : findAllSubscribers(subscriber).entrySet()) {
            Class eventType = entry.getKey();
            Set> eventMethods = entry.getValue();
            Map>> topicMap = subscribers.computeIfAbsent(eventType, k -> new ConcurrentHashMap<>(TOPIC_MAP_INITIAL_CAPACITY));
            for (Map.Entry>> subEntry : Linq.from(eventMethods).groupByIntoMap(p -> {
                Subscribe m = p.right.getAnnotation(Subscribe.class);
                if (m.topicClass() != Object.class) {
                    return m.topicClass();
                }
                if (!m.topic().isEmpty()) {
                    return m.topic();
                }
                return m.value();
            }, (p, x) -> x.toSet()).entrySet()) {
                topicMap.computeIfAbsent(subEntry.getKey(), k -> new CopyOnWriteArraySet<>()).addAll(subEntry.getValue());
            }
        }
    }

    public  void unregister(T subscriber) {
        unregister(subscriber, null);
    }

    public  void unregister(@NonNull T subscriber, TT topic) {
        boolean exist = false;
        for (Map.Entry, Set>> entry : findAllSubscribers(subscriber).entrySet()) {
            Class eventType = entry.getKey();
            Collection> eventMethods = entry.getValue();
            Map>> topicMap = subscribers.getOrDefault(eventType, Collections.emptyMap());
            if (topic == null) {
                for (Set> currentSubscribers : topicMap.values()) {
                    if (currentSubscribers.removeAll(eventMethods)) {
                        exist = true;
                    }
                }
            } else {
                Set> currentSubscribers = topicMap.get(topic);
                if (currentSubscribers != null && currentSubscribers.removeAll(eventMethods)) {
                    exist = true;
                }
            }
        }
        if (!exist) {
            throw new InvalidException("missing event subscriber for an annotated method. Is {}[{}] registered?", subscriber, topic);
        }
    }

    Map, Set>> findAllSubscribers(Object listener) {
        Map, Set>> methodsInListener = new HashMap<>();
        for (Method method : Linq.from(Reflects.getMethodMap(listener instanceof Class ? (Class) listener : listener.getClass()).values()).selectMany(p -> p).where(p -> p.isAnnotationPresent(Subscribe.class) && !p.isSynthetic())) {
            if (method.getParameterCount() != 1) {
                throw new InvalidException("Subscriber method {} has @Subscribe annotation must have exactly 1 parameter.", method);
            }
            Class eventType = method.getParameterTypes()[0];
            methodsInListener.computeIfAbsent(eventType, k -> new HashSet<>()).add(Tuple.of(listener, method));
        }
        return methodsInListener;
    }

    public  void publish(T event) {
        publish(event, getTopic(event));
    }

    public  void publish(@NonNull T event, TT topic) {
        log.debug("publish[{}] {}", topic, event);
        Class type = event.getClass();
        List> eventTypes = ClassUtils.getAllSuperclasses(type);
        eventTypes.add(type);

        Linq> q = Linq.from(eventTypes);
        Set> eventSubscribers = topic == null ? q.selectMany(p -> subscribers.getOrDefault(p, Collections.emptyMap()).values()).selectMany(p -> p).toSet() : q.selectMany(p -> subscribers.getOrDefault(p, Collections.emptyMap()).getOrDefault(topic, Collections.emptySet())).toSet();
        if (eventSubscribers.isEmpty()) {
            TraceHandler.INSTANCE.saveMetric(Constants.MetricName.DEAD_EVENT.name(), String.format("The event %s[%s] had no subscribers", event, topic));
            raiseEvent(onDeadEvent, new NEventArgs<>(event));
            return;
        }

        Extends.eachQuietly(eventSubscribers, p -> Reflects.invokeMethod(p.right, p.left, event));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy