package com.twasyl.slideshowfx.server.bus; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * This class provides a simple event bus that can be used in the whole SlideshowFX application in order to share events. * In order to share events, {@link Actor actors} must register to end points. * * @author Thierry Wasylczenko * @since SlideshowFX 1.0 * @version 1.0.0 */ public class EventBus { private static volatile EventBus singleton = null; private final Map<String, Set<Actor>> endPointsAndActorsMapping = new ConcurrentHashMap<>(); /** * Private constructor of the EventBus in order to implement the singleton pattern. * @see {@link #getInstance()} */ private EventBus() { } /** * Get an instance of {@link EventBus}. This method takes care of having only one single instance of {@link EventBus}. * @return The singleton instance of the {@link EventBus}. */ public synchronized static EventBus getInstance() { if(singleton == null) { singleton = new EventBus(); } return singleton; } /** * Check if a given endpoint is valid or not. The endpoint is considered invalid if it is {@code null} or its * trimmed value is empty. * @param endPoint The endpoint to check. * @throws NullPointerException If the endpoint is {@code null} * @throws IllegalArgumentException If the trimmed endpoint is empty. */ private void checkEndPointIsValid(final String endPoint) { if(endPoint == null) throw new NullPointerException("The endPoint can not be null"); final String trimmedEndPoint = endPoint.trim(); if(trimmedEndPoint.isEmpty()) throw new IllegalArgumentException("The endPoint can not be empty"); } /** * Check if a given {@link Actor actor} is valid or not. The {@link Actor actor} is considered valid if it is not * {@code null}. * @param actor The {@link Actor actor} to check. */ private void checkActorIsValid(final Actor actor) { if(actor == null) throw new NullPointerException("The actor can not be null"); } /** * Subscribe a given {@link Actor actor} to a given endpoint. Both endpoint and actor can not be {@code null}. * @param endPoint The endpoint to register the actor on. * @param actor The actor to subscribe. * @return This instance of {@link EventBus}. * @throws NullPointerException If either the endPoint or the actor is {@code null}. * @throws IllegalArgumentException If the endpoint is empty. */ public synchronized EventBus subscribe(final String endPoint, final Actor actor) { checkActorIsValid(actor); checkEndPointIsValid(endPoint); final String trimmedEndPoint = endPoint.trim(); if(!endPointsAndActorsMapping.containsKey(trimmedEndPoint)) { endPointsAndActorsMapping.put(trimmedEndPoint, new HashSet<>()); } this.endPointsAndActorsMapping.get(trimmedEndPoint).add(actor); return this; } /** * Unsubscribe a given {@link Actor actor} from a given endpoint. Both endpoint and actor can not be {@code null}. * @param endPoint The endpoint to unsubscribe the actor for. * @param actor The actor to unsubscribe. * @return This instance of {@link EventBus}. * @throws NullPointerException If either the endPoint or the actor is {@code null}. * @throws IllegalArgumentException If the endpoint is empty. */ public synchronized EventBus unsubscribe(final String endPoint, final Actor actor) { checkActorIsValid(actor); checkEndPointIsValid(endPoint); final String trimmedEndPoint = endPoint.trim(); if(endPointsAndActorsMapping.containsKey(trimmedEndPoint)) { endPointsAndActorsMapping.get(trimmedEndPoint).remove(actor); } return this; } /** * Broadcast a given message to all subscribers of a given endpoint. * @param endPoint The endpoint to send the message to. * @param message The message to send. * @throws NullPointerException If the endPoint is {@code null}. * @throws IllegalArgumentException If the endpoint is empty. */ public void broadcast(final String endPoint, final Object message) { checkEndPointIsValid(endPoint); final Set<Actor> actors = this.endPointsAndActorsMapping.get(endPoint.trim()); if(actors != null) { actors.forEach(actor -> { if(actor.supportsMessage(message)) { final Runnable runnable = () -> actor.onMessage(message); final Thread thread = new Thread(runnable); thread.start(); } }); } } }