Commit 4bf718669f177dfce2d1bd248920ef7c0ad2fe50

Authored by Mumfrey
1 parent 61965517

adding MessageBus

java/client/com/mumfrey/liteloader/client/api/LiteLoaderCoreAPIClient.java
... ... @@ -14,6 +14,7 @@ import com.mumfrey.liteloader.client.LiteLoaderCoreProviderClient;
14 14 import com.mumfrey.liteloader.core.LiteLoader;
15 15 import com.mumfrey.liteloader.core.api.LiteLoaderCoreAPI;
16 16 import com.mumfrey.liteloader.interfaces.ObjectFactory;
  17 +import com.mumfrey.liteloader.messaging.MessageBus;
17 18  
18 19 /**
19 20 * Client side of the core API
... ... @@ -113,7 +114,8 @@ public class LiteLoaderCoreAPIClient extends LiteLoaderCoreAPI
113 114 (
114 115 objectFactory.getEventBroker(),
115 116 objectFactory.getClientPluginChannels(),
116   - objectFactory.getServerPluginChannels()
  117 + objectFactory.getServerPluginChannels(),
  118 + MessageBus.getInstance()
117 119 );
118 120 }
119 121  
... ... @@ -125,7 +127,7 @@ public class LiteLoaderCoreAPIClient extends LiteLoaderCoreAPI
125 127 {
126 128 return ImmutableList.<Observer>of
127 129 (
128   - this.getObjectFactory().getModPanelManager()
  130 + this.getObjectFactory().getPanelManager()
129 131 );
130 132 }
131 133  
... ...
java/common/com/mumfrey/liteloader/core/LiteLoader.java
... ... @@ -46,6 +46,7 @@ import com.mumfrey.liteloader.interfaces.PanelManager;
46 46 import com.mumfrey.liteloader.interfaces.ObjectFactory;
47 47 import com.mumfrey.liteloader.launch.LoaderEnvironment;
48 48 import com.mumfrey.liteloader.launch.LoaderProperties;
  49 +import com.mumfrey.liteloader.messaging.MessageBus;
49 50 import com.mumfrey.liteloader.modconfig.ConfigManager;
50 51 import com.mumfrey.liteloader.modconfig.Exposable;
51 52 import com.mumfrey.liteloader.permissions.PermissionsManagerClient;
... ... @@ -848,7 +849,7 @@ public final class LiteLoader
848 849 }
849 850  
850 851 // Get the mod panel manager
851   - this.panelManager = this.objectFactory.getModPanelManager();
  852 + this.panelManager = this.objectFactory.getPanelManager();
852 853 if (this.panelManager != null)
853 854 {
854 855 this.panelManager.init(this.mods, this.configManager);
... ... @@ -905,15 +906,14 @@ public final class LiteLoader
905 906 // Set the loader branding in ClientBrandRetriever using reflection
906 907 LiteLoaderBootstrap.setBranding("LiteLoader");
907 908  
908   - for (CoreProvider coreProvider : this.coreProviders)
909   - {
910   - coreProvider.onStartupComplete();
911   - }
  909 + this.coreProviders.all().onStartupComplete();
912 910  
913 911 if (this.panelManager != null)
914 912 {
915 913 this.panelManager.onStartupComplete();
916 914 }
  915 +
  916 + MessageBus.getInstance().onStartupComplete();
917 917 }
918 918  
919 919 /**
... ...
java/common/com/mumfrey/liteloader/messaging/Message.java 0 → 100644
  1 +package com.mumfrey.liteloader.messaging;
  2 +
  3 +import java.util.HashMap;
  4 +import java.util.Map;
  5 +import java.util.regex.Pattern;
  6 +
  7 +import com.google.common.collect.ImmutableMap;
  8 +
  9 +/**
  10 + * Class used to encapsulate a MessageBus message
  11 + *
  12 + * @author Adam Mummery-Smith
  13 + */
  14 +public class Message
  15 +{
  16 + /**
  17 + * Regex for matching valid channels
  18 + */
  19 + private static final Pattern channelPattern = Pattern.compile("^[a-z0-9]([a-z0-9_\\-]*[a-z0-9])?:[a-z0-9]([a-z0-9_\\-]*[a-z0-9])?$", Pattern.CASE_INSENSITIVE);
  20 +
  21 + private final String channel, replyChannel;
  22 + private final Messenger sender;
  23 + private final Map<String, ?> payload;
  24 +
  25 + Message(String channel, Object value, Messenger sender)
  26 + {
  27 + this(channel, value, sender, null);
  28 + }
  29 +
  30 + Message(String channel, Object value, Messenger sender, String replyChannel)
  31 + {
  32 + Message.validateChannel(channel);
  33 +
  34 + this.channel = channel;
  35 + this.payload = ImmutableMap.<String, Object>of("value", value);
  36 + this.sender = sender;
  37 + this.replyChannel = replyChannel;
  38 + }
  39 +
  40 + Message(String channel, Map<String, ?> payload, Messenger sender)
  41 + {
  42 + this(channel, payload, sender, null);
  43 + }
  44 +
  45 + Message(String channel, Map<String, ?> payload, Messenger sender, String replyChannel)
  46 + {
  47 + Message.validateChannel(channel);
  48 +
  49 + this.channel = channel;
  50 + this.payload = payload != null ? ImmutableMap.copyOf(payload) : ImmutableMap.<String, String>of();
  51 + this.sender = sender;
  52 + this.replyChannel = replyChannel;
  53 + }
  54 +
  55 + /**
  56 + * Get the channel (fully qualified) that this message was sent on
  57 + */
  58 + public String getChannel()
  59 + {
  60 + return this.channel;
  61 + }
  62 +
  63 + /**
  64 + * Get the channel category for this message
  65 + */
  66 + public String getCategory()
  67 + {
  68 + return this.channel.substring(0, this.channel.indexOf(':'));
  69 + }
  70 +
  71 + /**
  72 + * Get the specified reply channel (if any) for this message - may return null
  73 + */
  74 + public String getReplyChannel()
  75 + {
  76 + return this.replyChannel;
  77 + }
  78 +
  79 + /**
  80 + * Get the message sender (if any) for this message - may return null
  81 + */
  82 + public Messenger getSender()
  83 + {
  84 + return this.sender;
  85 + }
  86 +
  87 + /**
  88 + * Get the message payload
  89 + */
  90 + public Map<String, ?> getPayload()
  91 + {
  92 + return this.payload;
  93 + }
  94 +
  95 + /**
  96 + * Check if this message is on the specified channel
  97 + *
  98 + * @param channel Full name of the channel to check against (case sensitive)
  99 + * @return
  100 + */
  101 + public boolean isChannel(String channel)
  102 + {
  103 + return this.channel.equals(channel);
  104 + }
  105 +
  106 + /**
  107 + * Check if this message has the specified category
  108 + *
  109 + * @param category
  110 + * @return
  111 + */
  112 + public boolean isCategory(String category)
  113 + {
  114 + return this.getCategory().equals(category);
  115 + }
  116 +
  117 + /**
  118 + * Get (and implicit cast) a value from this message's payload
  119 + *
  120 + * @param key
  121 + * @return
  122 + */
  123 + @SuppressWarnings("unchecked")
  124 + public <T> T get(String key)
  125 + {
  126 + return (T)this.payload.get(key);
  127 + }
  128 +
  129 + @SuppressWarnings("unchecked")
  130 + public <T> T get(String key, T defaultValue)
  131 + {
  132 + Object value = this.payload.get(key);
  133 + if (value != null)
  134 + {
  135 + return (T)value;
  136 + }
  137 + return defaultValue;
  138 + }
  139 +
  140 + /**
  141 + * Gets the payload with the key "value", which is used with messages constructed using a string-only payload
  142 + */
  143 + public <T> T getValue()
  144 + {
  145 + return this.get("value");
  146 + }
  147 +
  148 + public static void validateChannel(String channel) throws IllegalArgumentException
  149 + {
  150 + if (channel == null)
  151 + {
  152 + throw new IllegalArgumentException("Channel name cannot be null");
  153 + }
  154 +
  155 + if (!Message.isValidChannel(channel))
  156 + {
  157 + throw new IllegalArgumentException("'" + channel + "' is not a valid channel name");
  158 + }
  159 + }
  160 +
  161 + public static boolean isValidChannel(String channel)
  162 + {
  163 + return Message.channelPattern.matcher(channel).matches();
  164 + }
  165 +
  166 + /**
  167 + * Build a KV map from interleaved keys and values, convenience function
  168 + *
  169 + * @param args
  170 + * @return
  171 + */
  172 + public static Map<String, ?> buildMap(Object... args)
  173 + {
  174 + Map<String, Object> payload = new HashMap<String, Object>();
  175 + for (int i = 0; i < args.length - 1; i += 2)
  176 + {
  177 + if (args[i] instanceof String)
  178 + {
  179 + payload.put((String)args[i], args[i + 1]);
  180 + }
  181 + }
  182 +
  183 + return payload;
  184 + }
  185 +}
... ...
java/common/com/mumfrey/liteloader/messaging/MessageBus.java 0 → 100644
  1 +package com.mumfrey.liteloader.messaging;
  2 +
  3 +import java.util.Deque;
  4 +import java.util.HashMap;
  5 +import java.util.LinkedList;
  6 +import java.util.List;
  7 +import java.util.Map;
  8 +
  9 +import com.mumfrey.liteloader.api.InterfaceProvider;
  10 +import com.mumfrey.liteloader.api.Listener;
  11 +import com.mumfrey.liteloader.core.InterfaceRegistrationDelegate;
  12 +import com.mumfrey.liteloader.core.event.HandlerList;
  13 +import com.mumfrey.liteloader.interfaces.FastIterable;
  14 +import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
  15 +
  16 +/**
  17 + * Intra-mod messaging bus, allows mods to send arbitrary notifications to each other without having to
  18 + * create an explicit dependency or resort to reflection
  19 + *
  20 + * @author Adam Mummery-Smith
  21 + */
  22 +public class MessageBus implements InterfaceProvider
  23 +{
  24 + /**
  25 + * Singleton
  26 + */
  27 + private static MessageBus instance;
  28 +
  29 + /**
  30 + * Messengers subscribed to each channel
  31 + */
  32 + private final Map<String, FastIterable<Messenger>> messengers = new HashMap<String, FastIterable<Messenger>>();
  33 +
  34 + /**
  35 + * Pending messages dispatched pre-startup
  36 + */
  37 + private final Deque<Message> messageQueue = new LinkedList<Message>();
  38 +
  39 + private boolean enableMessaging = false;
  40 +
  41 + private MessageBus()
  42 + {
  43 + }
  44 +
  45 + /**
  46 + * Get the singleton instance
  47 + */
  48 + public static MessageBus getInstance()
  49 + {
  50 + if (MessageBus.instance == null)
  51 + {
  52 + MessageBus.instance = new MessageBus();
  53 + }
  54 +
  55 + return MessageBus.instance;
  56 + }
  57 +
  58 + /* (non-Javadoc)
  59 + * @see com.mumfrey.liteloader.api.InterfaceProvider#getListenerBaseType()
  60 + */
  61 + @Override
  62 + public Class<? extends Listener> getListenerBaseType()
  63 + {
  64 + return Listener.class;
  65 + }
  66 +
  67 + /* (non-Javadoc)
  68 + * @see com.mumfrey.liteloader.api.InterfaceProvider#registerInterfaces(com.mumfrey.liteloader.core.InterfaceRegistrationDelegate)
  69 + */
  70 + @Override
  71 + public void registerInterfaces(InterfaceRegistrationDelegate delegate)
  72 + {
  73 + delegate.registerInterface(Messenger.class);
  74 + }
  75 +
  76 + /* (non-Javadoc)
  77 + * @see com.mumfrey.liteloader.api.InterfaceProvider#initProvider()
  78 + */
  79 + @Override
  80 + public void initProvider()
  81 + {
  82 + }
  83 +
  84 + /**
  85 + *
  86 + */
  87 + public void onStartupComplete()
  88 + {
  89 + this.enableMessaging = true;
  90 +
  91 + while (this.messageQueue.size() > 0)
  92 + {
  93 + Message msg = this.messageQueue.pop();
  94 + this.dispatchMessage(msg);
  95 + }
  96 + }
  97 +
  98 + public void registerMessenger(Messenger messenger)
  99 + {
  100 + List<String> messageChannels = messenger.getMessageChannels();
  101 + if (messageChannels == null)
  102 + {
  103 + LiteLoaderLogger.warning("Listener %s returned a null channel list for getMessageChannels(), this could indicate a problem with the listener", messenger.getName());
  104 + return;
  105 + }
  106 +
  107 + for (String channel : messageChannels)
  108 + {
  109 + if (channel != null && Message.isValidChannel(channel))
  110 + {
  111 + LiteLoaderLogger.info("Listener %s is registering MessageBus channel %s", messenger.getName(), channel);
  112 + this.getMessengerList(channel).add(messenger);
  113 + }
  114 + else
  115 + {
  116 + LiteLoaderLogger.warning("Listener %s tried to register invalid MessageBus channel %s", messenger.getName(), channel);
  117 + }
  118 + }
  119 + }
  120 +
  121 + /**
  122 + * @param message
  123 + */
  124 + private void sendMessage(Message message)
  125 + {
  126 + if (this.enableMessaging)
  127 + {
  128 + this.dispatchMessage(message);
  129 + return;
  130 + }
  131 +
  132 + this.messageQueue.push(message);
  133 + }
  134 +
  135 + /**
  136 + * @param message
  137 + */
  138 + private void dispatchMessage(Message message)
  139 + {
  140 + try
  141 + {
  142 + FastIterable<Messenger> messengerList = this.messengers.get(message.getChannel());
  143 + if (messengerList != null)
  144 + {
  145 + messengerList.all().receiveMessage(message);
  146 + }
  147 + }
  148 + catch (StackOverflowError err)
  149 + {
  150 + // A listener tried to reply on the same channel and ended up calling itself
  151 + throw new RuntimeException("Stack overflow encountered dispatching message on channel '" + message.getChannel() + "'. Did you reply to yourself?");
  152 + }
  153 + }
  154 +
  155 + /**
  156 + * Get messengers for the specified channel
  157 + *
  158 + * @param channel
  159 + * @return
  160 + */
  161 + private FastIterable<Messenger> getMessengerList(String channel)
  162 + {
  163 + FastIterable<Messenger> messengerList = this.messengers.get(channel);
  164 + if (messengerList == null)
  165 + {
  166 + messengerList = new HandlerList<Messenger>(Messenger.class);
  167 + this.messengers.put(channel, messengerList);
  168 + }
  169 +
  170 + return messengerList;
  171 + }
  172 +
  173 + /**
  174 + * Send an empty message on the specified channel, this is useful for messages which are basically just notifications
  175 + *
  176 + * @param channel
  177 + */
  178 + public static void send(String channel)
  179 + {
  180 + Message message = new Message(channel, null, null);
  181 + MessageBus.getInstance().sendMessage(message);
  182 + }
  183 +
  184 + /**
  185 + * Send a message with a value on the specified channel
  186 + *
  187 + * @param channel
  188 + * @param value
  189 + */
  190 + public static void send(String channel, String value)
  191 + {
  192 + Message message = new Message(channel, value, null);
  193 + MessageBus.getInstance().sendMessage(message);
  194 + }
  195 +
  196 + /**
  197 + * Send a message with a value on the specified channel from the specified sender
  198 + *
  199 + * @param channel
  200 + * @param value
  201 + * @param sender
  202 + */
  203 + public static void send(String channel, String value, Messenger sender)
  204 + {
  205 + Message message = new Message(channel, value, sender);
  206 + MessageBus.getInstance().sendMessage(message);
  207 + }
  208 +
  209 + /**
  210 + * Send a message with a value on the specified channel from the specified sender
  211 + *
  212 + * @param channel
  213 + * @param value
  214 + * @param sender
  215 + * @param replyChannel
  216 + */
  217 + public static void send(String channel, String value, Messenger sender, String replyChannel)
  218 + {
  219 + Message message = new Message(channel, value, sender, replyChannel);
  220 + MessageBus.getInstance().sendMessage(message);
  221 + }
  222 +
  223 + /**
  224 + * Send a message with a supplied payload on the specified channel
  225 + *
  226 + * @param channel
  227 + * @param payload
  228 + */
  229 + public static void send(String channel, Map<String, ?> payload)
  230 + {
  231 + Message message = new Message(channel, payload, null);
  232 + MessageBus.getInstance().sendMessage(message);
  233 + }
  234 +
  235 + /**
  236 + * Send a message with a supplied payload on the specified channel from the specified sender
  237 + *
  238 + * @param channel
  239 + * @param payload
  240 + * @param sender
  241 + */
  242 + public static void send(String channel, Map<String, ?> payload, Messenger sender)
  243 + {
  244 + Message message = new Message(channel, payload, sender);
  245 + MessageBus.getInstance().sendMessage(message);
  246 + }
  247 +
  248 + /**
  249 + * Send a message with a supplied payload on the specified channel from the specified sender
  250 + *
  251 + * @param channel
  252 + * @param payload
  253 + * @param sender
  254 + * @param replyChannel
  255 + */
  256 + public static void send(String channel, Map<String, ?> payload, Messenger sender, String replyChannel)
  257 + {
  258 + Message message = new Message(channel, payload, sender, replyChannel);
  259 + MessageBus.getInstance().sendMessage(message);
  260 + }
  261 +}
... ...
java/common/com/mumfrey/liteloader/messaging/Messenger.java 0 → 100644
  1 +package com.mumfrey.liteloader.messaging;
  2 +
  3 +import java.util.List;
  4 +
  5 +import com.mumfrey.liteloader.api.Listener;
  6 +
  7 +/**
  8 + * Interface for listeners that want to receive (or send)
  9 + *
  10 + * @author Adam Mummery-Smith
  11 + */
  12 +public interface Messenger extends Listener
  13 +{
  14 + /**
  15 + * Get listening channels for this Messenger. Channel names must follow the format:
  16 + *
  17 + * {category}:{channel}
  18 + *
  19 + * where both {category} and {channel} are alpha-numeric identifiers which can contain underscore or dash
  20 + * but must begin and end with only alpha-numeric characters: for example the following channel names are
  21 + * valid:
  22 + *
  23 + * * foo:bar
  24 + * * foo-bar:baz
  25 + * * foo-bar:baz_derp
  26 + *
  27 + * The following are INVALID:
  28 + *
  29 + * * foo
  30 + * * foo_:bar
  31 + * * _foo:bar
  32 + *
  33 + * In general, your listener should listen on channels all beginning with the same category, which may match
  34 + * your mod id. Channel names and categories are case-sensitive.
  35 + *
  36 + * @return List of channels to listen on
  37 + */
  38 + public abstract List<String> getMessageChannels();
  39 +
  40 + /**
  41 + * Called when a message matching a channel you have elected to listen on is dispatched by any agent.
  42 + * WARNING: this method is called if you dispatch a message on a channel you are listening to, thus you should
  43 + * AVOID replying on channels you are listening to UNLESS you specifically filter messages based on their sender:
  44 + *
  45 + * if (message.getSender() == this) return;
  46 + *
  47 + * Messages may have a null sender or payload but will never have a null channel.
  48 + *
  49 + * @param message
  50 + */
  51 + public abstract void receiveMessage(Message message);
  52 +}
... ...