net.sradonia.eventbus.EventBus.java Source code

Java tutorial

Introduction

Here is the source code for net.sradonia.eventbus.EventBus.java

Source

/*******************************************************************************
 * sradonia tools
 * Copyright (C) 2012 Stefan Rado
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/
package net.sradonia.eventbus;

import java.util.Locale;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The EventBus core class.
 * 
 * @author Stefan Rado
 */
public class EventBus {
    private static Log log = LogFactory.getLog(EventBus.class);

    protected static HashMap<Object, EventBus> buses = new HashMap<Object, EventBus>();

    protected Set<VetoListener> vetoListeners;
    protected Map<Class<?>, Set<VetoListener>> vetoListenersForClass;
    protected Map<Class<?>, Set<VetoListener>> vetoListenersForExactClass;
    protected Map<Pattern, Set<VetoListener>> vetoListenersForTopic;
    protected Map<String, Set<VetoListener>> vetoListenersForExactTopic;

    protected Set<EventSubscriber> subscribers;
    protected Map<Class<?>, Set<EventSubscriber>> subscribersForClass;
    protected Map<Class<?>, Set<EventSubscriber>> subscribersForExactClass;
    protected Map<Pattern, Set<EventSubscriber>> subscribersForTopic;
    protected Map<String, Set<EventSubscriber>> subscribersForExactTopic;

    /**
     * <p>
     * Searches for an existing EventBus instance. If none is found, a new one will be created automatically.
     * </p>
     * 
     * <p>
     * The name is case-insensitive. It will be transformed to lower case before usage.
     * </p>
     * 
     * @param name
     *            the name of the eventbus
     * @return the searched eventbus instance
     */
    public static EventBus getEventBus(Object name) {
        if (name instanceof String) {
            name = ((String) name).toLowerCase(Locale.ENGLISH);
        }
        EventBus eb = buses.get(name);
        if (eb == null) {
            eb = new EventBus();
            log.info("created EventBus: " + name);
            buses.put(name, eb);
        }
        return eb;
    }

    /**
     * <p>
     * Creates a private EventBus instance which can't be retrieved later via the {@link #getEventBus(Object)} method.
     * </p>
     * 
     * @return a newly created private eventbus instance
     */
    public static EventBus getEventBus() {
        EventBus eb = new EventBus();
        log.info("created private EventBus: " + eb);
        return eb;
    }

    /**
     * Private constructor. To construct a new EventBus just use the static {@link #getEventBus(String)} method with an unused name or the
     * {@link #getEventBus()} method to obtain a private and unnamed instance.
     */
    private EventBus() {
    }

    /**
     * <p>
     * Subscribes the subscriber to <b>all</b> events published on this EventBus instance.
     * </p>
     * 
     * <p>
     * <b>This method should normally not be used until it really becomes necessary as it may produce a lot of needless subscriber calls!</b>
     * </p>
     * 
     * @param subscriber
     *            the event subscriber to add
     */
    public void subscribe(EventSubscriber subscriber) {
        if (subscribers == null)
            subscribers = new LinkedHashSet<EventSubscriber>();
        synchronized (subscribers) {
            subscribers.add(subscriber);
        }
        if (log.isInfoEnabled())
            log.info("added subscriber: " + subscriber);
    }

    /**
     * Subscribes the subscriber to all events published on this EventBus instance which are instance of or subclass of the specified event class.
     * 
     * @param clazz
     *            the event class to subscribe to
     * @param subscriber
     *            the event subscriber to add
     */
    public void subscribe(Class<?> clazz, EventSubscriber subscriber) {
        if (subscribersForClass == null)
            subscribersForClass = new LinkedHashMap<Class<?>, Set<EventSubscriber>>();
        synchronized (subscribersForClass) {
            Set<EventSubscriber> set = subscribersForClass.get(clazz);
            if (set == null) {
                set = new LinkedHashSet<EventSubscriber>();
                subscribersForClass.put(clazz, set);
            }
            set.add(subscriber);
        }
        if (log.isInfoEnabled())
            log.info("added subscriber to class [" + clazz + "]: " + subscriber);
    }

    /**
     * Subscribes the subscriber to all events published on this EventBus instance which are instance of the specified event class. The subscriber
     * will not get any events that is subclass of the subscribed event class.
     * 
     * @param clazz
     *            the event class to subscribe to
     * @param subscriber
     *            the event subscriber to add
     */
    public void subscribeExactly(Class<?> clazz, EventSubscriber subscriber) {
        if (subscribersForExactClass == null)
            subscribersForExactClass = new LinkedHashMap<Class<?>, Set<EventSubscriber>>();
        synchronized (subscribersForExactClass) {
            Set<EventSubscriber> set = subscribersForExactClass.get(clazz);
            if (set == null) {
                set = new LinkedHashSet<EventSubscriber>();
                subscribersForExactClass.put(clazz, set);
            }
            set.add(subscriber);
        }
        if (log.isInfoEnabled())
            log.info("added subscriber exactly to class [" + clazz + "]: " + subscriber);
    }

    /**
     * <p>
     * Subscribes the subscriber to all events which are published under topics matching the given regular expression.
     * </p>
     * 
     * <p>
     * A call to this method is equal to calling {@link #subscribe(Pattern, EventSubscriber)} with <code>Pattern.compile(topic)</code>.
     * </p>
     * 
     * @param topic
     *            regular expression used to determine wanted topics
     * @param subscriber
     *            the event subscriber to add
     */
    public void subscribe(String topic, EventSubscriber subscriber) {
        subscribe(Pattern.compile(topic), subscriber);
    }

    /**
     * Subscribes the subscriber to all events which are published under topics matching the given pattern.
     * 
     * @param topic
     *            regular expression used to determine wanted topics
     * @param subscriber
     *            the event subscriber to add
     */
    public void subscribe(Pattern topic, EventSubscriber subscriber) {
        if (subscribersForTopic == null)
            subscribersForTopic = new LinkedHashMap<Pattern, Set<EventSubscriber>>();
        synchronized (subscribersForTopic) {
            Set<EventSubscriber> set = subscribersForTopic.get(topic);
            if (set == null) {
                set = new LinkedHashSet<EventSubscriber>();
                subscribersForTopic.put(topic, set);
            }
            set.add(subscriber);
        }
        if (log.isInfoEnabled())
            log.info("added subscriber to topic [" + topic + "]: " + subscriber);
    }

    /**
     * <p>
     * Subscribes the subscriber to all events which are published using exactly the given topic.
     * </p>
     * 
     * <p>
     * The topics are compared using the {@link String#equals(Object)}</code> method.
     * </p>
     * 
     * @param topic
     *            regular expression used to determine wanted topics
     * @param subscriber
     *            the event subscriber to add
     */
    public void subscribeExactly(String topic, EventSubscriber subscriber) {
        if (subscribersForExactTopic == null)
            subscribersForExactTopic = new LinkedHashMap<String, Set<EventSubscriber>>();
        synchronized (subscribersForExactTopic) {
            Set<EventSubscriber> set = subscribersForExactTopic.get(topic);
            if (set == null) {
                set = new LinkedHashSet<EventSubscriber>();
                subscribersForExactTopic.put(topic, set);
            }
            set.add(subscriber);
        }
        if (log.isInfoEnabled())
            log.info("added subscriber exactly to topic [" + topic + "]: " + subscriber);
    }

    /**
     * <p>
     * Subscribes the veto listener to <b>all</b> events published on this EventBus instance.
     * </p>
     * 
     * <p>
     * <b>This method should normally not be used until it really becomes necessary as it may produce a lot of needless veto listener calls!</b>
     * </p>
     * 
     * @param listener
     *            the veto listener to add
     */
    public void subscribe(VetoListener listener) {
        if (vetoListeners == null) {
            vetoListeners = new LinkedHashSet<VetoListener>();
        }
        synchronized (vetoListeners) {
            vetoListeners.add(listener);
        }
        if (log.isInfoEnabled())
            log.info("added veto listener: " + listener);
    }

    /**
     * Subscribes the veto listener to all events published on this EventBus instance which are instance of or subclass of the specified event class.
     * 
     * @param clazz
     *            the event class to subscribe to
     * @param listener
     *            the veto listener to add
     */
    public void subscribe(Class<?> clazz, VetoListener listener) {
        if (vetoListenersForClass == null)
            vetoListenersForClass = new LinkedHashMap<Class<?>, Set<VetoListener>>();
        synchronized (vetoListenersForClass) {
            Set<VetoListener> set = vetoListenersForClass.get(clazz);
            if (set == null) {
                set = new LinkedHashSet<VetoListener>();
                vetoListenersForClass.put(clazz, set);
            }
            set.add(listener);
        }
        if (log.isInfoEnabled())
            log.info("added veto listener to class [" + clazz + "]: " + listener);
    }

    /**
     * Subscribes the veto listener to all events published on this EventBus instance which are instance of the specified event class. The veto
     * listener will not get any events that is subclass of the subscribed event class.
     * 
     * @param clazz
     *            the event class to subscribe to
     * @param listener
     *            the veto listener to add
     */
    public void subscribeExactly(Class<?> clazz, VetoListener listener) {
        if (vetoListenersForExactClass == null)
            vetoListenersForExactClass = new LinkedHashMap<Class<?>, Set<VetoListener>>();
        synchronized (vetoListenersForExactClass) {
            Set<VetoListener> set = vetoListenersForExactClass.get(clazz);
            if (set == null) {
                set = new LinkedHashSet<VetoListener>();
                vetoListenersForExactClass.put(clazz, set);
            }
            set.add(listener);
        }
        if (log.isInfoEnabled())
            log.info("added veto listener exactly to class [" + clazz + "]: " + listener);
    }

    /**
     * <p>
     * Subscribes the veto listener to all events which are published under topics matching the given regular expression.
     * </p>
     * 
     * <p>
     * A call to this method is equal to calling {@link #subscribe(Pattern, VetoListener)} with <code>Pattern.compile(topic)</code>.
     * </p>
     * 
     * @param topic
     *            regular expression used to determine wanted topics
     * @param listener
     *            the veto listener to add
     */
    public void subscribe(String topic, VetoListener listener) {
        subscribe(Pattern.compile(topic), listener);
    }

    /**
     * Subscribes the veto listener to all events which are published under topics matching the given pattern.
     * 
     * @param topic
     *            regular expression used to determine wanted topics
     * @param listener
     *            the veto listener to add
     */
    public void subscribe(Pattern topic, VetoListener listener) {
        if (vetoListenersForTopic == null)
            vetoListenersForTopic = new LinkedHashMap<Pattern, Set<VetoListener>>();
        synchronized (vetoListenersForTopic) {
            Set<VetoListener> set = vetoListenersForTopic.get(topic);
            if (set == null) {
                set = new LinkedHashSet<VetoListener>();
                vetoListenersForTopic.put(topic, set);
            }
            set.add(listener);
        }
        if (log.isInfoEnabled())
            log.info("added veto listener to topic [" + topic + "]: " + listener);
    }

    /**
     * <p>
     * Subscribes the veto listener to all events which are published using exactly the given topic.
     * </p>
     * 
     * <p>
     * The topics are compared using the {@link String#equals(Object)}</code> method.
     * </p>
     * 
     * @param topic
     *            regular expression used to determine wanted topics
     * @param listener
     *            the veto listener to add
     */
    public void subscribeExactly(String topic, VetoListener listener) {
        if (vetoListenersForExactTopic == null)
            vetoListenersForExactTopic = new LinkedHashMap<String, Set<VetoListener>>();
        synchronized (vetoListenersForExactTopic) {
            Set<VetoListener> set = vetoListenersForExactTopic.get(topic);
            if (set == null) {
                set = new LinkedHashSet<VetoListener>();
                vetoListenersForExactTopic.put(topic, set);
            }
            set.add(listener);
        }
        if (log.isInfoEnabled())
            log.info("added veto listener exactly to topic [" + topic + "]: " + listener);
    }

    /**
     * <p>
     * Publishes an event on the bus.
     * </p>
     * <p>
     * A <code>null</code> topic will be used, so a call to this method is equal to calling {@link #publish(String, Object) publish(null, event)}.
     * </p>
     * 
     * @param event
     *            the event to publish
     * @return <code>true</code> if the event has been published successfully, <code>false</code> if it has been vetoed
     */
    public boolean publish(Object event) {
        return publish(null, event);
    }

    /**
     * <p>
     * Publishes an event on the bus.
     * </p>
     * 
     * @param topic
     *            the topic of the event
     * @param event
     *            the event object
     * @return <code>true</code> if the event has been published successfully, <code>false</code> if it has been vetoed
     */
    public boolean publish(String topic, Object event) {
        if (event == null)
            throw new IllegalArgumentException("can't publish null event!");

        if (log.isInfoEnabled())
            log.info("publishing {topic=" + topic + ", event=" + event + "}");

        Class<?> eventClass = event.getClass();

        // check VetoListeners
        LinkedHashSet<VetoListener> vetoListeners = new LinkedHashSet<VetoListener>();
        if (this.vetoListeners != null) {
            synchronized (this.vetoListeners) {
                vetoListeners.addAll(this.vetoListeners);
            }
        }
        vetoListeners.addAll(getVetoListenersForClass(eventClass));
        if (topic != null)
            vetoListeners.addAll(getVetoListenersForTopic(topic));

        for (VetoListener vetoListener : vetoListeners) {
            try {
                if (vetoListener.shouldVeto(topic, event)) {
                    if (log.isInfoEnabled())
                        log.info(vetoListener + " vetoed event {topic=" + topic + ", event=" + event + "}");
                    return false;
                }
            } catch (RuntimeException e) {
                if (log.isErrorEnabled())
                    log.error(vetoListener + " threw an exception while checking for veto of event {topic=" + topic
                            + ", event=" + event + "}", e);
                throw e;
            }
        }

        // publish
        Set<EventSubscriber> subscribers = new LinkedHashSet<EventSubscriber>();
        if (this.subscribers != null) {
            synchronized (this.subscribers) {
                subscribers.addAll(this.subscribers);
            }
        }
        subscribers.addAll(getSubscribersForClass(eventClass));
        if (topic != null)
            subscribers.addAll(getSubscribersForTopic(topic));

        for (EventSubscriber subscriber : subscribers) {
            try {
                subscriber.onEvent(topic, event);
            } catch (RuntimeException e) {
                if (log.isErrorEnabled())
                    log.error(subscriber + " threw an exception while handling event {topic=" + topic + ", event="
                            + event + "}", e);
                throw e;
            }
        }

        return true;
    }

    /**
     * @param topic
     *            the topic for which to collect subscribers
     * @return a collection of matching subscribers
     */
    protected Set<EventSubscriber> getSubscribersForTopic(String topic) {
        Set<EventSubscriber> subscribers = new LinkedHashSet<EventSubscriber>();
        if (subscribersForExactTopic != null) {
            synchronized (subscribersForExactTopic) {
                Set<EventSubscriber> set = subscribersForExactTopic.get(topic);
                if (set != null)
                    subscribers.addAll(set);
            }
        }
        if (subscribersForTopic != null) {
            synchronized (subscribersForTopic) {
                for (Pattern p : subscribersForTopic.keySet()) {
                    if (p.matcher(topic).matches())
                        subscribers.addAll(subscribersForTopic.get(p));
                }
            }
        }
        return subscribers;
    }

    /**
     * @param clazz
     *            the class for which to collect subscribers
     * @return a collection of matching subscribers
     */
    protected Set<EventSubscriber> getSubscribersForClass(Class<?> clazz) {
        Set<EventSubscriber> subscribers = new LinkedHashSet<EventSubscriber>();
        if (subscribersForExactClass != null) {
            synchronized (subscribersForExactClass) {
                Set<EventSubscriber> set = subscribersForExactClass.get(clazz);
                if (set != null)
                    subscribers.addAll(set);
            }
        }
        if (subscribersForClass != null) {
            synchronized (subscribersForClass) {
                for (Class<?> c : subscribersForClass.keySet()) {
                    if (c.isAssignableFrom(clazz)) {
                        subscribers.addAll(subscribersForClass.get(c));
                    }
                }
            }
        }
        return subscribers;
    }

    /**
     * @param topic
     *            the topic for which to collect veto listeners
     * @return a collection of matching veto listeners
     */
    protected Set<VetoListener> getVetoListenersForTopic(String topic) {
        Set<VetoListener> listeners = new LinkedHashSet<VetoListener>();
        if (vetoListenersForExactTopic != null) {
            synchronized (vetoListenersForExactTopic) {
                Set<VetoListener> set = vetoListenersForExactTopic.get(topic);
                if (set != null)
                    listeners.addAll(set);
            }
        }
        if (vetoListenersForTopic != null) {
            synchronized (vetoListenersForTopic) {
                for (Pattern p : vetoListenersForTopic.keySet()) {
                    if (p.matcher(topic).matches())
                        listeners.addAll(vetoListenersForTopic.get(p));
                }
            }
        }
        return listeners;
    }

    /**
     * @param clazz
     *            the class for which to collect veto listeners
     * @return a collection of matching veto listeners
     */
    protected Set<VetoListener> getVetoListenersForClass(Class<?> clazz) {
        LinkedHashSet<VetoListener> listeners = new LinkedHashSet<VetoListener>();
        if (vetoListenersForExactClass != null) {
            synchronized (vetoListenersForExactClass) {
                Set<VetoListener> set = vetoListenersForExactClass.get(clazz);
                if (set != null)
                    listeners.addAll(set);
            }
        }
        if (vetoListenersForClass != null) {
            synchronized (vetoListenersForClass) {
                for (Class<?> c : vetoListenersForClass.keySet()) {
                    if (c.isAssignableFrom(clazz)) {
                        listeners.addAll(vetoListenersForClass.get(c));
                    }
                }
            }
        }
        return listeners;
    }
}