diff options
author | Nathan Adams <dinnerbone@dinnerbone.com> | 2012-01-13 08:51:10 +0000 |
---|---|---|
committer | Nathan Adams <dinnerbone@dinnerbone.com> | 2012-01-13 08:53:14 +0000 |
commit | ce4f4af087f55bed13c5821dd631b520a7286e8c (patch) | |
tree | e7d84ec7d0bc88f1dd04757379fb9fa158b35341 /src/main/java/org | |
parent | 389d70dbe2279253cf5ea5fcb97a6ed894f9e426 (diff) | |
download | bukkit-ce4f4af087f55bed13c5821dd631b520a7286e8c.tar bukkit-ce4f4af087f55bed13c5821dd631b520a7286e8c.tar.gz bukkit-ce4f4af087f55bed13c5821dd631b520a7286e8c.tar.lz bukkit-ce4f4af087f55bed13c5821dd631b520a7286e8c.tar.xz bukkit-ce4f4af087f55bed13c5821dd631b520a7286e8c.zip |
Implemented new Plugin Message API - see http://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/
Diffstat (limited to 'src/main/java/org')
15 files changed, 931 insertions, 3 deletions
diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java index d7753329..739c6099 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -17,6 +17,7 @@ import org.bukkit.inventory.Recipe; import org.bukkit.map.MapView; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; import org.bukkit.scheduler.BukkitScheduler; /** @@ -286,4 +287,8 @@ public final class Bukkit { public static File getWorldContainer() { return server.getWorldContainer(); } + + public static Messenger getMessenger() { + return server.getMessenger(); + } } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java index 54f301e3..6c78def2 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -20,12 +20,14 @@ import org.bukkit.command.ConsoleCommandSender; import org.bukkit.map.MapView; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicesManager; +import org.bukkit.plugin.messaging.Messenger; +import org.bukkit.plugin.messaging.PluginMessageRecipient; import org.bukkit.scheduler.BukkitScheduler; /** * Represents a server implementation */ -public interface Server { +public interface Server extends PluginMessageRecipient { /** * Used for all administrative messages, such as an operator using a command. * @@ -533,4 +535,11 @@ public interface Server { * @return Array containing all players */ public OfflinePlayer[] getOfflinePlayers(); + + /** + * Gets the {@link Messenger} responsible for this server. + * + * @return Messenger responsible for this server. + */ + public Messenger getMessenger(); } diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java index d6ae084d..4e2e34bf 100644 --- a/src/main/java/org/bukkit/World.java +++ b/src/main/java/org/bukkit/World.java @@ -12,12 +12,13 @@ import org.bukkit.block.Block; import org.bukkit.entity.*; import org.bukkit.generator.BlockPopulator; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.messaging.PluginMessageRecipient; import org.bukkit.util.Vector; /** * Represents a world, which may contain entities, chunks and blocks */ -public interface World { +public interface World extends PluginMessageRecipient { /** * Gets the {@link Block} at the given coordinates diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java index 8fbb9509..26b42dce 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java @@ -12,12 +12,13 @@ import org.bukkit.OfflinePlayer; import org.bukkit.Statistic; import org.bukkit.command.CommandSender; import org.bukkit.map.MapView; +import org.bukkit.plugin.messaging.PluginMessageRecipient; /** * Represents a player, connected or not * */ -public interface Player extends HumanEntity, CommandSender, OfflinePlayer { +public interface Player extends HumanEntity, CommandSender, OfflinePlayer, PluginMessageRecipient { /** * Gets the "friendly" name to display of this player. This may include color. * diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java index 7d7f1c2b..c70bc22b 100644 --- a/src/main/java/org/bukkit/plugin/PluginManager.java +++ b/src/main/java/org/bukkit/plugin/PluginManager.java @@ -8,6 +8,7 @@ import org.bukkit.event.Event.Priority; import org.bukkit.event.Listener; import org.bukkit.permissions.Permissible; import org.bukkit.permissions.Permission; +import org.bukkit.plugin.messaging.PluginMessageListener; /** * Handles all plugin management from the Server diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java new file mode 100644 index 00000000..76e06fc1 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java @@ -0,0 +1,14 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Channel is too long. + */ +public class ChannelNameTooLongException extends RuntimeException { + public ChannelNameTooLongException() { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars."); + } + + public ChannelNameTooLongException(String channel) { + super("Attempted to send a Plugin Message to a channel that was too large. The maximum length a channel may be is " + Messenger.MAX_CHANNEL_SIZE + " chars (attempted " + channel.length() + " - '" + channel + "."); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java new file mode 100644 index 00000000..a7a568e9 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java @@ -0,0 +1,14 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin attempts to send a message on an unregistered channel. + */ +public class ChannelNotRegisteredException extends RuntimeException { + public ChannelNotRegisteredException() { + this("Attempted to send a plugin message through an unregistered channel."); + } + + public ChannelNotRegisteredException(String channel) { + super("Attempted to send a plugin message through an unregistered channel ('" + channel + "'."); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java new file mode 100644 index 00000000..3a195c05 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java @@ -0,0 +1,22 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a Plugin Message is sent that is too large to be sent. + */ +public class MessageTooLargeException extends RuntimeException { + public MessageTooLargeException() { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes."); + } + + public MessageTooLargeException(byte[] message) { + this(message.length); + } + + public MessageTooLargeException(int length) { + this("Attempted to send a plugin message that was too large. The maximum length a plugin message may be is " + Messenger.MAX_MESSAGE_SIZE + " bytes (tried to send one that is " + length + " bytes long)."); + } + + public MessageTooLargeException(String msg) { + super(msg); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/Messenger.java b/src/main/java/org/bukkit/plugin/messaging/Messenger.java new file mode 100644 index 00000000..5083587e --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/Messenger.java @@ -0,0 +1,201 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * A class responsible for managing the registrations of plugin channels and their + * listeners. + */ +public interface Messenger { + /** + * Represents the largest size that an individual Plugin Message may be. + */ + public static final int MAX_MESSAGE_SIZE = 32766; + + /** + * Represents the largest size that a Plugin Channel may be. + */ + public static final int MAX_CHANNEL_SIZE = 16; + + /** + * Checks if the specified channel is a reserved name. + * + * @param channel Channel name to check. + * @return True if the channel is reserved, otherwise false. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public boolean isReservedChannel(String channel); + + /** + * Registers the specific plugin to the requested outgoing plugin channel, allowing it + * to send messages through that channel to any clients. + * + * @param plugin Plugin that wishes to send messages through the channel. + * @param channel Channel to register. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void registerOutgoingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from the requested outgoing plugin channel, no longer + * allowing it to send messages through that channel to any clients. + * + * @param plugin Plugin that no longer wishes to send messages through the channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterOutgoingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from all outgoing plugin channels, no longer allowing + * it to send any plugin messages. + * + * @param plugin Plugin that no longer wishes to send plugin messages. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterOutgoingPluginChannel(Plugin plugin); + + /** + * Registers the specific plugin for listening on the requested incoming plugin channel, + * allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to register to this channel. + * @param channel Channel to register. + * @param listener Listener to receive messages on. + * @returns The resulting registration that was made as a result of this method. + * @throws IllegalArgumentException Thrown if plugin, channel or listener is null, or the listener is already registered for this channel. + */ + public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener); + + /** + * Unregisters the specific plugin's listener from listening on the requested incoming plugin channel, + * no longer allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @param listener Listener to stop receiving messages on. + * @throws IllegalArgumentException Thrown if plugin, channel or listener is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener); + + /** + * Unregisters the specific plugin from listening on the requested incoming plugin channel, + * no longer allowing it to act upon any plugin messages. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @param channel Channel to unregister. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin, String channel); + + /** + * Unregisters the specific plugin from listening on all plugin channels through all listeners. + * + * @param plugin Plugin that wishes to unregister from this channel. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public void unregisterIncomingPluginChannel(Plugin plugin); + + /** + * Gets a set containing all the outgoing plugin channels. + * + * @return List of all registered outgoing plugin channels. + */ + public Set<String> getOutgoingChannels(); + + /** + * Gets a set containing all the outgoing plugin channels that the specified plugin is registered to. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered outgoing plugin channels that a plugin is registered to. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set<String> getOutgoingChannels(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channels. + * + * @return List of all registered incoming plugin channels. + */ + public Set<String> getIncomingChannels(); + + /** + * Gets a set containing all the incoming plugin channels that the specified plugin is registered for. + * + * @param plugin Plugin to retrieve channels for. + * @return List of all registered incoming plugin channels that the plugin is registered for. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set<String> getIncomingChannels(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations that the specified plugin has. + * + * @param plugin Plugin to retrieve registrations for. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin is null. + */ + public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(Plugin plugin); + + /** + * Gets a set containing all the incoming plugin channel registrations that are on the requested channel. + * + * @param channel Channel to retrieve registrations for. + * @return List of all registrations that are on the channel. + * @throws IllegalArgumentException Thrown if channel is null. + */ + public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(String channel); + + /** + * Gets a set containing all the incoming plugin channel registrations that the specified plugin has + * on the requested channel. + * + * @param plugin Plugin to retrieve registrations for. + * @param channel Channel to filter registrations by. + * @return List of all registrations that the plugin has. + * @throws IllegalArgumentException Thrown if plugin or channel is null. + */ + public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(Plugin plugin, String channel); + + /** + * Checks if the specified plugin message listener registration is valid. + * <p> + * A registration is considered valid if it has not be unregistered and that the plugin + * is still enabled. + * + * @param registration Registration to check. + * @return True if the registration is valid, otherwise false. + */ + public boolean isRegistrationValid(PluginMessageListenerRegistration registration); + + /** + * Checks if the specified plugin has registered to receive incoming messages through the requested + * channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isIncomingChannelRegistered(Plugin plugin, String channel); + + /** + * Checks if the specified plugin has registered to send outgoing messages through the requested + * channel. + * + * @param plugin Plugin to check registration for. + * @param channel Channel to test for. + * @return True if the channel is registered, else false. + */ + public boolean isOutgoingChannelRegistered(Plugin plugin, String channel); + + /** + * Dispatches the specified incoming message to any registered listeners. + * + * @param source Source of the message. + * @param channel Channel that the message was sent by. + * @param message Raw payload of the message. + */ + public void dispatchIncomingMessage(Player source, String channel, byte[] message); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java b/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java new file mode 100644 index 00000000..3b385484 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java @@ -0,0 +1,16 @@ +package org.bukkit.plugin.messaging; + +/** + * Represents the different directions a plugin channel may go. + */ +public enum PluginChannelDirection { + /** + * The plugin channel is being sent to the server from a client. + */ + INCOMING, + + /** + * The plugin channel is being sent to a client from the server. + */ + OUTGOING +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java new file mode 100644 index 00000000..0e197a6d --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java @@ -0,0 +1,19 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.entity.Player; + +/** + * A listener for a specific Plugin Channel, which will receive notifications of messages sent + * from a client. + */ +public interface PluginMessageListener { + /** + * A method that will be thrown when a {@link PluginMessageSource} sends a plugin + * message on a registered channel. + * + * @param channel Channel that the message was sent through. + * @param player Source of the message. + * @param message The raw message that was sent. + */ + public void onPluginMessageReceived(String channel, Player player, byte[] message); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java new file mode 100644 index 00000000..f736ce99 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java @@ -0,0 +1,103 @@ +package org.bukkit.plugin.messaging; + +import org.bukkit.plugin.Plugin; + +/** + * Contains information about a {@link Plugin}s registration to a plugin channel. + */ +public final class PluginMessageListenerRegistration { + private final Messenger messenger; + private final Plugin plugin; + private final String channel; + private final PluginMessageListener listener; + + public PluginMessageListenerRegistration(Messenger messenger, Plugin plugin, String channel, PluginMessageListener listener) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null!"); + } + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null!"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null!"); + } + + this.messenger = messenger; + this.plugin = plugin; + this.channel = channel; + this.listener = listener; + } + + /** + * Gets the plugin channel that this registration is about. + * + * @return Plugin channel. + */ + public String getChannel() { + return channel; + } + + /** + * Gets the registered listener described by this registration. + * + * @return Registered listener. + */ + public PluginMessageListener getListener() { + return listener; + } + + /** + * Gets the plugin that this registration is for. + * + * @return Registered plugin. + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Checks if this registration is still valid. + * + * @return True if this registration is still valid, otherwise false. + */ + public boolean isValid() { + return messenger.isRegistrationValid(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PluginMessageListenerRegistration other = (PluginMessageListenerRegistration) obj; + if (this.messenger != other.messenger && (this.messenger == null || !this.messenger.equals(other.messenger))) { + return false; + } + if (this.plugin != other.plugin && (this.plugin == null || !this.plugin.equals(other.plugin))) { + return false; + } + if ((this.channel == null) ? (other.channel != null) : !this.channel.equals(other.channel)) { + return false; + } + if (this.listener != other.listener && (this.listener == null || !this.listener.equals(other.listener))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + (this.messenger != null ? this.messenger.hashCode() : 0); + hash = 53 * hash + (this.plugin != null ? this.plugin.hashCode() : 0); + hash = 53 * hash + (this.channel != null ? this.channel.hashCode() : 0); + hash = 53 * hash + (this.listener != null ? this.listener.hashCode() : 0); + return hash; + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java new file mode 100644 index 00000000..6383166b --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java @@ -0,0 +1,32 @@ +package org.bukkit.plugin.messaging; + +import java.util.Set; +import org.bukkit.plugin.Plugin; + +/** + * Represents a possible recipient for a Plugin Message. + */ +public interface PluginMessageRecipient { + /** + * Sends this recipient a Plugin Message on the specified outgoing channel. + * <p> + * The message may not be larger than {@link Messenger#MAX_MESSAGE_SIZE} bytes, and the plugin must be registered to send + * messages on the specified channel. + * + * @param source The plugin that sent this message. + * @param channel The channel to send this message on. + * @param message The raw message to send. + * @throws IllegalArgumentException Thrown if the source plugin is disabled. + * @throws IllegalArgumentException Thrown if source, channel or message is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNotRegisteredException Thrown if the channel is not registered for this plugin. + */ + public void sendPluginMessage(Plugin source, String channel, byte[] message); + + /** + * Gets a set containing all the Plugin Channels that this client is listening on. + * + * @return Set containing all the channels that this client may accept. + */ + public Set<String> getListeningPluginChannels(); +} diff --git a/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java new file mode 100644 index 00000000..8b20b41a --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java @@ -0,0 +1,14 @@ +package org.bukkit.plugin.messaging; + +/** + * Thrown if a plugin attempts to register for a reserved channel (such as "REGISTER") + */ +public class ReservedChannelException extends RuntimeException { + public ReservedChannelException() { + this("Attempted to register for a reserved channel name."); + } + + public ReservedChannelException(String name) { + super("Attempted to register for a reserved channel name ('" + name + "')"); + } +} diff --git a/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java new file mode 100644 index 00000000..e8ff0840 --- /dev/null +++ b/src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java @@ -0,0 +1,476 @@ +package org.bukkit.plugin.messaging; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * Standard implementation to {@link Messenger} + */ +public class StandardMessenger implements Messenger { + private final Map<String, Set<PluginMessageListenerRegistration>> incomingByChannel = new HashMap<String, Set<PluginMessageListenerRegistration>>(); + private final Map<Plugin, Set<PluginMessageListenerRegistration>> incomingByPlugin = new HashMap<Plugin, Set<PluginMessageListenerRegistration>>(); + private final Map<String, Set<Plugin>> outgoingByChannel = new HashMap<String, Set<Plugin>>(); + private final Map<Plugin, Set<String>> outgoingByPlugin = new HashMap<Plugin, Set<String>>(); + private final Object incomingLock = new Object(); + private final Object outgoingLock = new Object(); + + private void addToOutgoing(Plugin plugin, String channel) { + synchronized (outgoingLock) { + Set<Plugin> plugins = outgoingByChannel.get(channel); + Set<String> channels = outgoingByPlugin.get(plugin); + + if (plugins == null) { + plugins = new HashSet<Plugin>(); + outgoingByChannel.put(channel, plugins); + } + + if (channels == null) { + channels = new HashSet<String>(); + outgoingByPlugin.put(plugin, channels); + } + + plugins.add(plugin); + channels.add(channel); + } + } + + private void removeFromOutgoing(Plugin plugin, String channel) { + synchronized (outgoingLock) { + Set<Plugin> plugins = outgoingByChannel.get(channel); + Set<String> channels = outgoingByPlugin.get(plugin); + + if (plugins != null) { + plugins.remove(plugin); + + if (plugins.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + + if (channels != null) { + channels.remove(channel); + + if (channels.isEmpty()) { + outgoingByChannel.remove(channel); + } + } + } + } + + private void removeFromOutgoing(Plugin plugin) { + synchronized (outgoingLock) { + Set<String> channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + String[] toRemove = channels.toArray(new String[0]); + + outgoingByPlugin.remove(plugin); + + for (String channel : toRemove) { + removeFromOutgoing(plugin, channel); + } + } + } + } + + private void addToIncoming(PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations == null) { + registrations = new HashSet<PluginMessageListenerRegistration>(); + incomingByChannel.put(registration.getChannel(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations == null) { + registrations = new HashSet<PluginMessageListenerRegistration>(); + incomingByPlugin.put(registration.getPlugin(), registrations); + } else { + if (registrations.contains(registration)) { + throw new IllegalArgumentException("This registration already exists"); + } + } + + registrations.add(registration); + } + } + + private void removeFromIncoming(PluginMessageListenerRegistration registration) { + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByChannel.get(registration.getChannel()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByChannel.remove(registration.getChannel()); + } + } + + registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + registrations.remove(registration); + + if (registrations.isEmpty()) { + incomingByPlugin.remove(registration.getPlugin()); + } + } + } + } + + private void removeFromIncoming(Plugin plugin, String channel) { + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]); + + for (PluginMessageListenerRegistration registration : toRemove) { + if (registration.getChannel().equals(channel)) { + removeFromIncoming(registration); + } + } + } + } + } + + private void removeFromIncoming(Plugin plugin) { + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + PluginMessageListenerRegistration[] toRemove = registrations.toArray(new PluginMessageListenerRegistration[0]); + + incomingByPlugin.remove(plugin); + + for (PluginMessageListenerRegistration registration : toRemove) { + removeFromIncoming(registration); + } + } + } + } + + public boolean isReservedChannel(String channel) { + validateChannel(channel); + + return channel.equals("REGISTER") || channel.equals("UNREGISTER"); + } + + public void registerOutgoingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + + addToOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + removeFromOutgoing(plugin, channel); + } + + public void unregisterOutgoingPluginChannel(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromOutgoing(plugin); + } + + public PluginMessageListenerRegistration registerIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + if (isReservedChannel(channel)) { + throw new ReservedChannelException(channel); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + + PluginMessageListenerRegistration result = new PluginMessageListenerRegistration(this, plugin, channel, listener); + + addToIncoming(result); + + return result; + } + + public void unregisterIncomingPluginChannel(Plugin plugin, String channel, PluginMessageListener listener) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener cannot be null"); + } + validateChannel(channel); + + removeFromIncoming(new PluginMessageListenerRegistration(this, plugin, channel, listener)); + } + + public void unregisterIncomingPluginChannel(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + removeFromIncoming(plugin, channel); + } + + public void unregisterIncomingPluginChannel(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + removeFromIncoming(plugin); + } + + public Set<String> getOutgoingChannels() { + synchronized (outgoingLock) { + Set<String> keys = outgoingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + public Set<String> getOutgoingChannels(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (outgoingLock) { + Set<String> channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return ImmutableSet.copyOf(channels); + } else { + return ImmutableSet.of(); + } + } + } + + public Set<String> getIncomingChannels() { + synchronized (incomingLock) { + Set<String> keys = incomingByChannel.keySet(); + return ImmutableSet.copyOf(keys); + } + } + + public Set<String> getIncomingChannels(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder<String> builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + builder.add(registration.getChannel()); + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(String channel) { + validateChannel(channel); + + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByChannel.get(channel); + + if (registrations != null) { + return ImmutableSet.copyOf(registrations); + } else { + return ImmutableSet.of(); + } + } + } + + public Set<PluginMessageListenerRegistration> getIncomingChannelRegistrations(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + Builder<PluginMessageListenerRegistration> builder = ImmutableSet.builder(); + + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + builder.add(registration); + } + } + + return builder.build(); + } else { + return ImmutableSet.of(); + } + } + } + + public boolean isRegistrationValid(PluginMessageListenerRegistration registration) { + if (registration == null) { + throw new IllegalArgumentException("Registration cannot be null"); + } + + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(registration.getPlugin()); + + if (registrations != null) { + return registrations.contains(registration); + } + + return false; + } + } + + public boolean isIncomingChannelRegistered(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (incomingLock) { + Set<PluginMessageListenerRegistration> registrations = incomingByPlugin.get(plugin); + + if (registrations != null) { + for (PluginMessageListenerRegistration registration : registrations) { + if (registration.getChannel().equals(channel)) { + return true; + } + } + } + + return false; + } + } + + public boolean isOutgoingChannelRegistered(Plugin plugin, String channel) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + validateChannel(channel); + + synchronized (outgoingLock) { + Set<String> channels = outgoingByPlugin.get(plugin); + + if (channels != null) { + return channels.contains(channel); + } + + return false; + } + } + + public void dispatchIncomingMessage(Player source, String channel, byte[] message) { + if (source == null) { + throw new IllegalArgumentException("Player source cannot be null"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + validateChannel(channel); + + Set<PluginMessageListenerRegistration> registrations = getIncomingChannelRegistrations(channel); + + for (PluginMessageListenerRegistration registration : registrations) { + registration.getListener().onPluginMessageReceived(channel, source, message); + } + } + + /** + * Validates a Plugin Channel name. + * + * @param channel Channel name to validate. + */ + public static void validateChannel(String channel) { + if (channel == null) { + throw new IllegalArgumentException("Channel cannot be null"); + } + if (channel.length() > Messenger.MAX_CHANNEL_SIZE) { + throw new ChannelNameTooLongException(channel); + } + } + + /** + * Validates the input of a Plugin Message, ensuring the arguments are all valid. + * + * @param messenger Messenger to use for validation. + * @param source Source plugin of the Message. + * @param channel Plugin Channel to send the message by. + * @param message Raw message payload to send. + * @throws IllegalArgumentException Thrown if the source plugin is disabled. + * @throws IllegalArgumentException Thrown if source, channel or message is null. + * @throws MessageTooLargeException Thrown if the message is too big. + * @throws ChannelNameTooLongException Thrown if the channel name is too long. + * @throws ChannelNotRegisteredException Thrown if the channel is not registered for this plugin. + */ + public static void validatePluginMessage(Messenger messenger, Plugin source, String channel, byte[] message) { + if (messenger == null) { + throw new IllegalArgumentException("Messenger cannot be null"); + } + if (source == null) { + throw new IllegalArgumentException("Plugin source cannot be null"); + } + if (!source.isEnabled()) { + throw new IllegalArgumentException("Plugin must be enabled to send messages"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } + if (!messenger.isOutgoingChannelRegistered(source, channel)) { + throw new ChannelNotRegisteredException(channel); + } + if (message.length > Messenger.MAX_MESSAGE_SIZE) { + throw new MessageTooLargeException(message); + } + validateChannel(channel); + } +} |