package eu.hgross.blaubot.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import eu.hgross.blaubot.admin.AbstractAdminMessage; import eu.hgross.blaubot.admin.AddSubscriptionAdminMessage; import eu.hgross.blaubot.admin.RemoveSubscriptionAdminMessage; import eu.hgross.blaubot.core.IBlaubotDevice; import eu.hgross.blaubot.core.ILifecycleListener; import eu.hgross.blaubot.messaging.IBlaubotAdminMessageListener; /** * Keeps track of subscriptions to channels by checking admin messages and the LifeCycleListeners. * It can be used to sniff the current subscriptions and channels out of the admin messages and * eventing system at runtime. */ public class ChannelSubscriptionListener implements IBlaubotAdminMessageListener, ILifecycleListener { private Map<Short, Set<String>> subscriptions = new ConcurrentHashMap<>(); private Object lock = new Object(); private List<SubscriptionChangeListener> listeners = new CopyOnWriteArrayList<>(); public interface SubscriptionChangeListener { /** * Called when the list of subscriptions to this channel changed * * @param channelId the channel id on which a subscription changed */ void onSubscriptionChanged(short channelId); /** * Called when a subscription was removed due to actively unsubscribing * or a disconnect of the device * @param channelId the channel * @param unqiueDeviceId the former subscriber's uniqueDeviceId */ void onUnsubscribed(short channelId, String unqiueDeviceId); /** * Gets called if a subscription was actively made. * @param channelId the channel * @param uniqueDeviceId the new subscriber */ void onSubscribed(short channelId, String uniqueDeviceId); } /** * The set of known channels that are used * @return list of channels that we know of */ public Set<Short> getChannels() { return subscriptions.keySet(); } /** * creates a set of the known subscribers to a specific channel id. * @param channelId the channel to retrieve the list of subscribers for * @return the set of subscribers for the given channel id */ public Set<String> getSubscribersOfChannel(short channelId) { return subscriptions.get(channelId); } @Override public void onAdminMessage(AbstractAdminMessage adminMessage) { if(adminMessage instanceof AddSubscriptionAdminMessage) { AddSubscriptionAdminMessage addSubscriptionAdminMessage = (AddSubscriptionAdminMessage) adminMessage; final short channelId = addSubscriptionAdminMessage.getChannelId(); addSubscription(channelId, addSubscriptionAdminMessage.getUniqueDeviceId()); } else if (adminMessage instanceof RemoveSubscriptionAdminMessage) { RemoveSubscriptionAdminMessage removeSubscriptionAdminMessage = (RemoveSubscriptionAdminMessage) adminMessage; final short channelId = removeSubscriptionAdminMessage.getChannelId(); removeSubscription(channelId, removeSubscriptionAdminMessage.getUniqueDeviceId()); } } private void addSubscription(short channelId, String uniqueDeviceId) { synchronized (lock) { ((ConcurrentHashMap)subscriptions).putIfAbsent(channelId, new HashSet<String>()); Set<String> subscribers = subscriptions.get(channelId); subscribers.add(uniqueDeviceId); } for(SubscriptionChangeListener listener : listeners) { listener.onSubscriptionChanged(channelId); listener.onSubscribed(channelId, uniqueDeviceId); } } private void removeSubscription(short channelId, String uniqueDeviceId) { synchronized (lock) { Set<String> subscribers = subscriptions.get(channelId); if(subscribers != null) { subscribers.remove(uniqueDeviceId); } } for(SubscriptionChangeListener listener : listeners) { listener.onSubscriptionChanged(channelId); listener.onUnsubscribed(channelId, uniqueDeviceId); } } @Override public void onConnected() { } @Override public void onDisconnected() { synchronized (lock) { subscriptions.clear(); } } @Override public void onDeviceJoined(IBlaubotDevice blaubotDevice) { } @Override public void onDeviceLeft(IBlaubotDevice blaubotDevice) { List<Short> unsubscribedChannels = new ArrayList<>(); // clear subscriptions regarding this device synchronized (lock) { for (Map.Entry<Short, Set<String>> entry : subscriptions.entrySet()) { final Set<String> subscribers = entry.getValue(); final short channelId = entry.getKey(); subscribers.remove(blaubotDevice.getUniqueDeviceID()); unsubscribedChannels.add(channelId); } } for(Short channelId : unsubscribedChannels) { for(SubscriptionChangeListener listener : listeners) { listener.onSubscriptionChanged(channelId); listener.onSubscribed(channelId, blaubotDevice.getUniqueDeviceID()); } } } @Override public void onPrinceDeviceChanged(IBlaubotDevice oldPrince, IBlaubotDevice newPrince) { } @Override public void onKingDeviceChanged(IBlaubotDevice oldKing, IBlaubotDevice newKing) { } /** * Adds a listener to be informed when the subscriptions change. * * @param changeListener the listener to be added */ public void addSubscriptionChangeListener(SubscriptionChangeListener changeListener) { listeners.add(changeListener); } /** * Removes a formerly added listener * * @param changeListener the listener to be removed */ public void removeSubscriptionChangeListener(SubscriptionChangeListener changeListener) { listeners.remove(changeListener); } };