summaryrefslogtreecommitdiffstats
path: root/src/main/java/org
diff options
context:
space:
mode:
authorNathan Adams <dinnerbone@dinnerbone.com>2012-01-13 08:51:10 +0000
committerNathan Adams <dinnerbone@dinnerbone.com>2012-01-13 08:53:14 +0000
commitce4f4af087f55bed13c5821dd631b520a7286e8c (patch)
treee7d84ec7d0bc88f1dd04757379fb9fa158b35341 /src/main/java/org
parent389d70dbe2279253cf5ea5fcb97a6ed894f9e426 (diff)
downloadbukkit-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')
-rw-r--r--src/main/java/org/bukkit/Bukkit.java5
-rw-r--r--src/main/java/org/bukkit/Server.java11
-rw-r--r--src/main/java/org/bukkit/World.java3
-rw-r--r--src/main/java/org/bukkit/entity/Player.java3
-rw-r--r--src/main/java/org/bukkit/plugin/PluginManager.java1
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java14
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java14
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java22
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/Messenger.java201
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/PluginChannelDirection.java16
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java19
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/PluginMessageListenerRegistration.java103
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/PluginMessageRecipient.java32
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java14
-rw-r--r--src/main/java/org/bukkit/plugin/messaging/StandardMessenger.java476
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);
+ }
+}