listening = this.server.getMessenger().getIncomingChannels();
+ if (!listening.isEmpty()) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ for (String channel : listening) {
+ try {
+ out.write(channel.getBytes(StandardCharsets.UTF_8));
+ out.write((byte) 0);
+ } catch (IOException e) {
+ MinecraftServer.log.log(Level.SEVERE, "Failed to send plugin channel REGISTER to " + getName(), e);
+ }
+ }
+
+ getHandle().netServerHandler.sendPacket(new Packet250PluginMessage("REGISTER", out.toByteArray()));
+ }
+ }
+ // Tsunami end
+
public String getDisplayName() {
return getHandle().displayName;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
new file mode 100644
index 0000000..492000f
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
@@ -0,0 +1,110 @@
+package org.bukkit.craftbukkit.persistence;
+
+import net.minecraft.server.NBTBase;
+import net.minecraft.server.NBTTagCompound;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+public class CraftPersistentDataContainer implements PersistentDataContainer {
+
+ public static final String TAG_KEY = "PersistentDataContainer";
+ private static final PersistentDataTypeRegistry REGISTRY = new PersistentDataTypeRegistry();
+
+ private final NBTTagCompound compound;
+
+ public CraftPersistentDataContainer() {
+ this.compound = new NBTTagCompound();
+ }
+
+ public CraftPersistentDataContainer(NBTTagCompound compound) {
+ this.compound = compound;
+ }
+
+ @Override
+ public void set(String key, PersistentDataType
type, C value) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+ Objects.requireNonNull(value, "value must not be null");
+
+ NBTBase tag = REGISTRY.getOrCreateAdapter(type).serialize(type.toPrimitive(value));
+ this.compound.a(key, tag);
+ }
+
+ @Override
+ public void remove(String key) {
+ Objects.requireNonNull(key, "key must not be null");
+
+ this.compound.a.remove(key);
+ }
+
+ @Override
+ public
boolean has(String key, PersistentDataType
type) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+
+ NBTBase tag = (NBTBase) this.compound.a.get(key);
+ return tag != null && REGISTRY.getOrCreateAdapter(type).matches(tag);
+ }
+
+ @Override
+ public boolean has(String key) {
+ Objects.requireNonNull(key, "key must not be null");
+
+ return this.compound.a.containsKey(key);
+ }
+
+ @Override
+ public
C get(String key, PersistentDataType
type) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+
+ NBTBase tag = (NBTBase) this.compound.a.get(key);
+ if (tag == null) {
+ return null;
+ }
+ P primitive = REGISTRY.getOrCreateAdapter(type).deserialize(tag);
+ return type.fromPrimitive(primitive);
+ }
+
+ @Override
+ public
C getOrDefault(String key, PersistentDataType
type, C defaultValue) {
+ Objects.requireNonNull(key, "key must not be null");
+ Objects.requireNonNull(type, "type must not be null");
+ Objects.requireNonNull(defaultValue, "defaultValue must not be null");
+
+ C value = get(key, type);
+ return value != null ? value : defaultValue;
+ }
+
+ @Override
+ public Set getKeys() {
+ return Collections.unmodifiableSet(new HashSet<>(this.compound.a.keySet()));
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.compound.a.isEmpty();
+ }
+
+ @Override
+ public void copyTo(PersistentDataContainer other, boolean replace) {
+ Objects.requireNonNull(other, "other must not be null");
+
+ CraftPersistentDataContainer target = (CraftPersistentDataContainer) other;
+ if (replace) {
+ target.asCompound().a.putAll(this.compound.a);
+ } else {
+ this.compound.a.forEach(target.asCompound().a::putIfAbsent);
+ }
+ }
+
+ public NBTTagCompound asCompound() {
+ return this.compound;
+ }
+
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java b/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java
new file mode 100644
index 0000000..e8d8569
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/persistence/PersistentDataTypeRegistry.java
@@ -0,0 +1,97 @@
+package org.bukkit.craftbukkit.persistence;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import net.minecraft.server.NBTBase;
+import net.minecraft.server.NBTTagByte;
+import net.minecraft.server.NBTTagByteArray;
+import net.minecraft.server.NBTTagCompound;
+import net.minecraft.server.NBTTagDouble;
+import net.minecraft.server.NBTTagFloat;
+import net.minecraft.server.NBTTagInt;
+import net.minecraft.server.NBTTagList;
+import net.minecraft.server.NBTTagLong;
+import net.minecraft.server.NBTTagShort;
+import net.minecraft.server.NBTTagString;
+import org.bukkit.persistence.ListPersistentDataType;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class PersistentDataTypeRegistry {
+
+ private final Map, PrimitiveToTagAdapter, ?>> adapters = new HashMap<>();
+
+ public synchronized PrimitiveToTagAdapter
getOrCreateAdapter(PersistentDataType
type) {
+ PrimitiveToTagAdapter
adapter = (PrimitiveToTagAdapter
) this.adapters.get(type);
+ if (adapter == null) {
+ adapter = createAdapter(type);
+ this.adapters.put(type, adapter);
+ }
+ return adapter;
+ }
+
+ private
PrimitiveToTagAdapter createAdapter(PersistentDataType
type) {
+ if (Byte.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagByte::new, tag -> tag.a, tag -> tag instanceof NBTTagByte
+ );
+ } else if (Short.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagShort::new, tag -> tag.a, tag -> tag instanceof NBTTagShort
+ );
+ } else if (Integer.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagInt::new, tag -> tag.a, tag -> tag instanceof NBTTagInt
+ );
+ } else if (Long.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagLong::new, tag -> tag.a, tag -> tag instanceof NBTTagLong
+ );
+ } else if (Float.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagFloat::new, tag -> tag.a, tag -> tag instanceof NBTTagFloat
+ );
+ } else if (Double.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagDouble::new, tag -> tag.a, tag -> tag instanceof NBTTagDouble
+ );
+ } else if (String.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagString::new, tag -> tag.a, tag -> tag instanceof NBTTagString
+ );
+ } else if (byte[].class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ NBTTagByteArray::new, tag -> tag.a, tag -> tag instanceof NBTTagByteArray
+ );
+ } else if (PersistentDataContainer.class.equals(type.getPrimitiveType())) {
+ return new PrimitiveToTagAdapter(
+ CraftPersistentDataContainer::asCompound, CraftPersistentDataContainer::new, tag -> tag instanceof NBTTagCompound
+ );
+ } else if (List.class.equals(type.getPrimitiveType())) {
+ Preconditions.checkArgument(type instanceof ListPersistentDataType, "type must be a ListPersistentDataType");
+ ListPersistentDataType, ?> listType = (ListPersistentDataType, ?>) type;
+ PrimitiveToTagAdapter elementAdapter = getOrCreateAdapter(listType.getElementType());
+
+ return new PrimitiveToTagAdapter(
+ list -> {
+ NBTTagList tag = new NBTTagList();
+ list.forEach(p -> tag.a(elementAdapter.serialize(p)));
+ return tag;
+ },
+ tag -> Lists.transform(tag.a, e -> {
+ Preconditions.checkState(elementAdapter.matches((NBTBase) e));
+ return elementAdapter.deserialize((NBTBase) e);
+ }),
+ tag -> tag instanceof NBTTagList
+ );
+ } else {
+ throw new IllegalArgumentException("illegal primitive type " + type.getClass().getName());
+ }
+ }
+
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java b/src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java
new file mode 100644
index 0000000..966b126
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/persistence/PrimitiveToTagAdapter.java
@@ -0,0 +1,32 @@
+package org.bukkit.craftbukkit.persistence;
+
+import net.minecraft.server.NBTBase;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class PrimitiveToTagAdapter {
+
+ private final Function
serializer;
+ private final Function deserializer;
+ private final Predicate matcher;
+
+ public PrimitiveToTagAdapter(Function serializer, Function deserializer, Predicate matcher) {
+ this.serializer = serializer;
+ this.deserializer = deserializer;
+ this.matcher = matcher;
+ }
+
+ public T serialize(P primitive) {
+ return this.serializer.apply(primitive);
+ }
+
+ public P deserialize(T tag) {
+ return this.deserializer.apply(tag);
+ }
+
+ public boolean matches(T tag) {
+ return this.matcher.test(tag);
+ }
+
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java b/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java
index ffb01f3..1afdf01 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ShortConsoleLogFormatter.java
@@ -52,7 +52,7 @@ public String format(LogRecord record) {
builder.append(record.getLevel().getLocalizedName().toUpperCase());
builder.append("] ");
builder.append(formattedMessage);
- builder.append('\n');
+ // Tsunami - removed builder.append('\n');
if (ex != null) {
StringWriter writer = new StringWriter();
diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
index c307f5e..9793609 100644
--- a/src/main/java/org/bukkit/entity/Entity.java
+++ b/src/main/java/org/bukkit/entity/Entity.java
@@ -5,6 +5,7 @@
import org.bukkit.World;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.metadata.Metadatable;
+import org.bukkit.persistence.PersistentDataHolder;
import org.bukkit.util.Vector;
import java.util.List;
@@ -13,7 +14,7 @@
/**
* Represents a base entity in the world
*/
-public interface Entity extends Metadatable { // Tsunami - extends Metadatable
+public interface Entity extends PersistentDataHolder, Metadatable { // Tsunami - extends PersistentDataHolder, Metadatable
/**
* Gets the entity's current position
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
index 9e3e591..da3061a 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -5,6 +5,7 @@
import org.bukkit.*;
import org.bukkit.command.CommandSender;
import org.bukkit.map.MapView;
+import org.bukkit.plugin.messaging.PluginMessageRecipient;
import java.net.InetSocketAddress;
import java.util.UUID;
@@ -12,7 +13,7 @@
/**
* Represents a player, connected or not
*/
-public interface Player extends HumanEntity, CommandSender, OfflinePlayer {
+public interface Player extends HumanEntity, CommandSender, OfflinePlayer, PluginMessageRecipient { // Tsunami - extends PluginMessageRecipient
/**
* Gets the "friendly" name to display of this player. This may include color.
*
diff --git a/src/main/java/org/bukkit/event/Event.java b/src/main/java/org/bukkit/event/Event.java
index 3d73d80..3c3607d 100644
--- a/src/main/java/org/bukkit/event/Event.java
+++ b/src/main/java/org/bukkit/event/Event.java
@@ -337,6 +337,20 @@ public enum Type {
* @see org.bukkit.event.player.PlayerItemDamageEvent
*/
PLAYER_ITEM_DAMAGE(Category.PLAYER),
+ // Tsunami start - backport plugin messaging
+ /**
+ * Called when a player registers for a plugin channel
+ *
+ * @see org.bukkit.event.player.PlayerRegisterChannelEvent
+ */
+ PLAYER_REGISTER_CHANNEL(Category.PLAYER),
+ /**
+ * Called when a player unregisters for a plugin channel
+ *
+ * @see org.bukkit.event.player.PlayerUnregisterChannelEvent
+ */
+ PLAYER_UNREGISTER_CHANNEL(Category.PLAYER),
+ // Tsunami end
/**
* BLOCK EVENTS
diff --git a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java
index b1e21f9..57267fe 100644
--- a/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java
+++ b/src/main/java/org/bukkit/event/entity/ProjectileHitEvent.java
@@ -1,14 +1,84 @@
package org.bukkit.event.entity;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.Entity;
import org.bukkit.entity.Projectile;
+import org.bukkit.event.Cancellable;
/**
- * Called when a projectile hits an object
+ * Called when a projectile hits a block or an entity
*/
-public class ProjectileHitEvent extends EntityEvent {
+public class ProjectileHitEvent extends EntityEvent implements Cancellable { // Tsunami - implements Cancellable
- public ProjectileHitEvent(Projectile projectile) {
+ // Tsunami start - improve ProjectileHitEvent
+ private final Projectile projectile;
+ private final Entity hitEntity;
+ private final Block hitBlock;
+ private final BlockFace hitFace;
+ private boolean cancelled = false;
+
+ public ProjectileHitEvent(Projectile projectile, Entity hitEntity) {
+ super(Type.PROJECTILE_HIT, projectile);
+ this.projectile = projectile;
+ this.hitEntity = hitEntity;
+ this.hitBlock = null;
+ this.hitFace = null;
+ }
+
+ public ProjectileHitEvent(Projectile projectile, Block hitBlock, BlockFace hitFace) {
super(Type.PROJECTILE_HIT, projectile);
+ this.projectile = projectile;
+ this.hitBlock = hitBlock;
+ this.hitFace = hitFace;
+ this.hitEntity = null;
+ }
+
+ /**
+ * Gets the projectile involved in this event
+ *
+ * @return the projectile
+ */
+ public Projectile getProjectile() {
+ return this.projectile;
+ }
+
+ /**
+ * Gets the entity that was hit, if it was an entity that was hit
+ *
+ * @return hit entity or else {@code null}
+ */
+ public Entity getHitEntity() {
+ return this.hitEntity;
+ }
+
+ /**
+ * Gets the block that was hit, if it was a block that was hit
+ *
+ * @return hit block or else {@code null}
+ */
+ public Block getHitBlock() {
+ return this.hitBlock;
+ }
+
+ /**
+ * Gets the block face that was hit, if it was a block that was hit
+ *
+ * @return hit face or else {@code null}
+ */
+ public BlockFace getHitBlockFace() {
+ return this.hitFace;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
}
+ // Tsunami end
}
diff --git a/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java
new file mode 100644
index 0000000..1036e5e
--- /dev/null
+++ b/src/main/java/org/bukkit/event/player/PlayerChannelEvent.java
@@ -0,0 +1,20 @@
+package org.bukkit.event.player;
+
+import org.bukkit.entity.Player;
+
+/**
+ * This event is called after a player registers or unregisters a new plugin
+ * channel.
+ */
+public abstract class PlayerChannelEvent extends PlayerEvent {
+ private final String channel;
+
+ public PlayerChannelEvent(Type type, Player player, String channel) {
+ super(type, player);
+ this.channel = channel;
+ }
+
+ public final String getChannel() {
+ return channel;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/event/player/PlayerListener.java b/src/main/java/org/bukkit/event/player/PlayerListener.java
index b7fce6c..ee90895 100644
--- a/src/main/java/org/bukkit/event/player/PlayerListener.java
+++ b/src/main/java/org/bukkit/event/player/PlayerListener.java
@@ -205,4 +205,20 @@ public void onPlayerFish(PlayerFishEvent event) {}
* @param event Relevant event details
*/
public void onPlayerItemDamage(PlayerItemDamageEvent event) {}
+
+ // Tsunami start - backport plugin messaging
+ /**
+ * Called when a player registers for a plugin channel
+ *
+ * @param event Relevant event details
+ */
+ public void onPlayerRegisterChannel(PlayerRegisterChannelEvent event) {}
+
+ /**
+ * Called when a player unregisters for a plugin channel
+ *
+ * @param event Relevant event details
+ */
+ public void onPlayerUnregisterChannel(PlayerUnregisterChannelEvent event) {}
+ // Tsunami end
}
diff --git a/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java
new file mode 100644
index 0000000..26a8275
--- /dev/null
+++ b/src/main/java/org/bukkit/event/player/PlayerRegisterChannelEvent.java
@@ -0,0 +1,13 @@
+package org.bukkit.event.player;
+
+import org.bukkit.entity.Player;
+
+/**
+ * This is called immediately after a player registers for a plugin channel.
+ */
+public class PlayerRegisterChannelEvent extends PlayerChannelEvent {
+
+ public PlayerRegisterChannelEvent(Player player, String channel) {
+ super(Type.PLAYER_REGISTER_CHANNEL, player, channel);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java
index 727041c..062716b 100644
--- a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java
+++ b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java
@@ -22,7 +22,8 @@ public PlayerTeleportEvent(final Event.Type type, Player player, Location from,
//Poseidon - Start
private void blockCrossDimensionDupe() {
- if (this.getFrom().getWorld() != this.getTo().getWorld()) {
+ // Tsunami - fix NPE
+ if (this.getTo() != null && this.getFrom().getWorld() != this.getTo().getWorld()) {
EntityPlayer entity = ((CraftPlayer) this.getPlayer()).getHandle();
if (entity.activeContainer == entity.defaultContainer)
return;
diff --git a/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java
new file mode 100644
index 0000000..5ec9029
--- /dev/null
+++ b/src/main/java/org/bukkit/event/player/PlayerUnregisterChannelEvent.java
@@ -0,0 +1,13 @@
+package org.bukkit.event.player;
+
+import org.bukkit.entity.Player;
+
+/**
+ * This is called immediately after a player unregisters for a plugin channel.
+ */
+public class PlayerUnregisterChannelEvent extends PlayerChannelEvent {
+
+ public PlayerUnregisterChannelEvent(Player player, String channel) {
+ super(Type.PLAYER_UNREGISTER_CHANNEL, player, channel);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/metadata/ByteMetadataValue.java b/src/main/java/org/bukkit/metadata/ByteMetadataValue.java
index 719fac1..2df84be 100644
--- a/src/main/java/org/bukkit/metadata/ByteMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/ByteMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#BYTE
+ */
+@Deprecated
public class ByteMetadataValue extends MetadataValue {
public ByteMetadataValue(byte value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java b/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java
index ddb67ae..bcadeec 100644
--- a/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/DoubleMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#DOUBLE
+ */
+@Deprecated
public class DoubleMetadataValue extends MetadataValue {
public DoubleMetadataValue(double value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/FloatMetadataValue.java b/src/main/java/org/bukkit/metadata/FloatMetadataValue.java
index 428d4af..dd2e6b0 100644
--- a/src/main/java/org/bukkit/metadata/FloatMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/FloatMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#FLOAT
+ */
+@Deprecated
public class FloatMetadataValue extends MetadataValue {
public FloatMetadataValue(float value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/IntMetadataValue.java b/src/main/java/org/bukkit/metadata/IntMetadataValue.java
index 9738544..a51f06e 100644
--- a/src/main/java/org/bukkit/metadata/IntMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/IntMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#INTEGER
+ */
+@Deprecated
public class IntMetadataValue extends MetadataValue {
public IntMetadataValue(int value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/LongMetadataValue.java b/src/main/java/org/bukkit/metadata/LongMetadataValue.java
index cc3a685..b4e80e3 100644
--- a/src/main/java/org/bukkit/metadata/LongMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/LongMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#LONG
+ */
+@Deprecated
public class LongMetadataValue extends MetadataValue {
public LongMetadataValue(long value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java
index d85d566..7be3bda 100644
--- a/src/main/java/org/bukkit/metadata/MetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/MetadataValue.java
@@ -2,7 +2,11 @@
/**
* Represents a metadata value of a {@link Metadatable} object
+ *
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType
*/
+@Deprecated
public abstract class MetadataValue {
private final Object value;
diff --git a/src/main/java/org/bukkit/metadata/Metadatable.java b/src/main/java/org/bukkit/metadata/Metadatable.java
index 23c2be4..26d5e1e 100644
--- a/src/main/java/org/bukkit/metadata/Metadatable.java
+++ b/src/main/java/org/bukkit/metadata/Metadatable.java
@@ -4,7 +4,11 @@
/**
* Represents an object that can provide metadata about itself
+ *
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataContainer
*/
+@Deprecated
public interface Metadatable {
/**
@@ -13,7 +17,9 @@ public interface Metadatable {
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
* @param value the metadata value
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#set(String, org.bukkit.persistence.PersistentDataType, Object)}
*/
+ @Deprecated
void setMetadata(Plugin owningPlugin, String key, MetadataValue value);
/**
@@ -21,7 +27,9 @@ public interface Metadatable {
*
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#remove(String)}
*/
+ @Deprecated
void removeMetadata(Plugin owningPlugin, String key);
/**
@@ -30,7 +38,9 @@ public interface Metadatable {
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
* @return the metadata value, or null if it does not exist
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#get(String, org.bukkit.persistence.PersistentDataType)}
*/
+ @Deprecated
MetadataValue getMetadata(Plugin owningPlugin, String key);
/**
@@ -39,7 +49,9 @@ public interface Metadatable {
* @param owningPlugin the plugin owning the metadata
* @param key the unique identifier for the metadata
* @return true if the metadata exists, false if not
+ * @deprecated use {@link org.bukkit.persistence.PersistentDataContainer#has(String)}
*/
+ @Deprecated
boolean hasMetadata(Plugin owningPlugin, String key);
}
diff --git a/src/main/java/org/bukkit/metadata/ShortMetadataValue.java b/src/main/java/org/bukkit/metadata/ShortMetadataValue.java
index 440207e..1dc5d4d 100644
--- a/src/main/java/org/bukkit/metadata/ShortMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/ShortMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#SHORT
+ */
+@Deprecated
public class ShortMetadataValue extends MetadataValue {
public ShortMetadataValue(short value) {
super(value);
diff --git a/src/main/java/org/bukkit/metadata/StringMetadataValue.java b/src/main/java/org/bukkit/metadata/StringMetadataValue.java
index 4f19d83..2fef59a 100644
--- a/src/main/java/org/bukkit/metadata/StringMetadataValue.java
+++ b/src/main/java/org/bukkit/metadata/StringMetadataValue.java
@@ -1,5 +1,10 @@
package org.bukkit.metadata;
+/**
+ * @deprecated This API has been superseded by {@link org.bukkit.persistence}.
+ * @see org.bukkit.persistence.PersistentDataType#STRING
+ */
+@Deprecated
public class StringMetadataValue extends MetadataValue {
public StringMetadataValue(String value) {
super(value);
diff --git a/src/main/java/org/bukkit/persistence/ListPersistentDataType.java b/src/main/java/org/bukkit/persistence/ListPersistentDataType.java
new file mode 100644
index 0000000..eccd851
--- /dev/null
+++ b/src/main/java/org/bukkit/persistence/ListPersistentDataType.java
@@ -0,0 +1,85 @@
+package org.bukkit.persistence;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Represents a data type which is used to convert a list of complex values
+ * to a list of primitive values, and vice versa. This is used by {@link PersistentDataContainer}
+ * for storage and retrieval of values of type {@link List}.
+ *
+ * Allowed primitive types are {@code Byte}, {@code Short}, {@code Integer},
+ * {@code Long}, {@code Float}, {@code Double}, {@code String}, {@code byte[]}
+ * and {@code PersistentDataContainer}.
+ *
+ * @see PersistentDataContainer
+ * @param
the primitive element type
+ * @param the complex element type
+ */
+public interface ListPersistentDataType extends PersistentDataType, List> {
+
+ ListPersistentDataType BYTE = listTypeFrom(PersistentDataType.BYTE);
+ ListPersistentDataType SHORT = listTypeFrom(PersistentDataType.SHORT);
+ ListPersistentDataType INTEGER = listTypeFrom(PersistentDataType.INTEGER);
+ ListPersistentDataType LONG = listTypeFrom(PersistentDataType.LONG);
+ ListPersistentDataType FLOAT = listTypeFrom(PersistentDataType.FLOAT);
+ ListPersistentDataType DOUBLE = listTypeFrom(PersistentDataType.DOUBLE);
+ ListPersistentDataType BOOLEAN = listTypeFrom(PersistentDataType.BOOLEAN);
+ ListPersistentDataType CHARACTER = listTypeFrom(PersistentDataType.CHARACTER);
+ ListPersistentDataType STRING = listTypeFrom(PersistentDataType.STRING);
+ ListPersistentDataType BYTE_ARRAY = listTypeFrom(PersistentDataType.BYTE_ARRAY);
+ ListPersistentDataType DATA_CONTAINER = listTypeFrom(PersistentDataType.DATA_CONTAINER);
+
+ /**
+ * Returns the data type which elements of a list of this type conform to.
+ *
+ * @return the element data type
+ */
+ PersistentDataType getElementType();
+
+ /**
+ * Creates a {@link ListPersistentDataType} from the specified element data type.
+ *
+ * @param type the element data type
+ * @return a new list data type
+ */
+ static
ListPersistentDataType
listTypeFrom(PersistentDataType
type) {
+ return new ListPersistentDataTypeImpl<>(type);
+ }
+
+ final class ListPersistentDataTypeImpl
implements ListPersistentDataType
{
+ private final PersistentDataType
elementType;
+
+ ListPersistentDataTypeImpl(PersistentDataType
elementType) {
+ this.elementType = elementType;
+ }
+
+ @Override
+ public Class> getPrimitiveType() {
+ return (Class>) (Object) List.class;
+ }
+
+ @Override
+ public Class> getComplexType() {
+ return (Class>) (Object) List.class;
+ }
+
+ @Override
+ public List toPrimitive(List complex) {
+ return complex.stream().map(this.elementType::toPrimitive).collect(Collectors.toList());
+ }
+
+ @Override
+ public List fromPrimitive(List primitive) {
+ return Lists.transform(primitive, this.elementType::fromPrimitive);
+ }
+
+ @Override
+ public PersistentDataType
getElementType() {
+ return this.elementType;
+ }
+ }
+
+}
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataContainer.java b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java
new file mode 100644
index 0000000..cdc91a2
--- /dev/null
+++ b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java
@@ -0,0 +1,104 @@
+package org.bukkit.persistence;
+
+import java.util.Set;
+
+/**
+ * Represents a container which stores persistent data using key-value mappings.
+ *
+ * A container can store simple values, but it can also store lists of values
+ * and even other containers, enabling complex and nested data structures
+ * to be stored.
+ *
+ * A distinction is made between primitive and complex types: when storing a
+ * value, a {@link PersistentDataType} must be provided in order to convert
+ * the complex value into a primitive value. When retrieving a value,
+ * the primitive value is converted back to a complex value.
+ *
+ * @see PersistentDataType
+ */
+public interface PersistentDataContainer {
+
+ /**
+ * Stores a new key-value mapping in this container, or replaces the value
+ * if a mapping with the specified key is already present.
+ *
+ * @param key the unique key of the mapping
+ * @param type the {@link PersistentDataType} used to convert
+ * the complex value to a primitive value
+ * @param value the complex value
+ */
+
void set(String key, PersistentDataType
type, C value);
+
+ /**
+ * Removes a key-value mapping from this container if it is present.
+ *
+ * @param key the unique key of the mapping to remove
+ */
+ void remove(String key);
+
+ /**
+ * Tests if a key-value mapping with the specified key and a value conforming
+ * to the {@link PersistentDataType} is present in this container.
+ *
+ * @param key the unique key of the mapping to test for
+ * @param type the {@link PersistentDataType} which the value should conform to
+ * @return {@code true} if a key-value mapping with this key and whose value
+ * conforms to the type exists
+ */
+
boolean has(String key, PersistentDataType
type);
+
+ /**
+ * Tests if a key-value mapping with the specified key is present in this container.
+ *
+ * @param key the unique key of the mapping to test for
+ * @return {@code true} if a key-value mapping with this key exists
+ */
+ boolean has(String key);
+
+ /**
+ * Retrieves a value from a key-value mapping present in this container.
+ *
+ * @param key the unique key of the mapping
+ * @param type the {@link PersistentDataType} used to convert
+ * the primitive value to a complex value
+ * @return the value associated with this key, or {@code null}
+ * if no mapping with this key exists
+ */
+
C get(String key, PersistentDataType
type);
+
+ /**
+ * Retrieves a value from a key-value mapping present in this container,
+ * or returns the specified default value if no such mapping exists.
+ *
+ * @param key the unique key of the mapping
+ * @param type the {@link PersistentDataType} used to convert
+ * the primitive value to a complex value
+ * @param defaultValue the value to return if the mapping is not present
+ * @return the value associated with this key, or {@code defaultValue}
+ * if no mapping with this key exists
+ */
+
C getOrDefault(String key, PersistentDataType
type, C defaultValue);
+
+ /**
+ * Returns a copy of the keys of all mappings present in this container.
+ *
+ * @return the keys of all mappings in this container
+ */
+ Set getKeys();
+
+ /**
+ * Tests if this container holds no key-value mappings.
+ *
+ * @return {@code true} if no mappings are present in this container
+ */
+ boolean isEmpty();
+
+ /**
+ * Copies all key-value mappings present in this container to another container.
+ *
+ * @param other the container to copy this container's mappings to
+ * @param replace if mappings from this container should replace mappings
+ * which are already present in the other container
+ */
+ void copyTo(PersistentDataContainer other, boolean replace);
+}
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
new file mode 100644
index 0000000..85aee7c
--- /dev/null
+++ b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java
@@ -0,0 +1,17 @@
+package org.bukkit.persistence;
+
+/**
+ * Represents an object which is able to store persistent data.
+ *
+ * @see PersistentDataContainer
+ */
+public interface PersistentDataHolder {
+
+ /**
+ * Returns the {@link PersistentDataContainer} which holds all persistent data
+ * stored by this object.
+ *
+ * @return this object's {@link PersistentDataContainer}
+ */
+ PersistentDataContainer getPersistentDataContainer();
+}
diff --git a/src/main/java/org/bukkit/persistence/PersistentDataType.java b/src/main/java/org/bukkit/persistence/PersistentDataType.java
new file mode 100644
index 0000000..25e0c1f
--- /dev/null
+++ b/src/main/java/org/bukkit/persistence/PersistentDataType.java
@@ -0,0 +1,134 @@
+package org.bukkit.persistence;
+
+/**
+ * Represents a data type which is used to convert a complex value to a
+ * primitive value, and vice versa. This is used by {@link PersistentDataContainer}
+ * for storage and retrieval of values.
+ *
+ * Allowed primitive types are {@code Byte}, {@code Short}, {@code Integer},
+ * {@code Long}, {@code Float}, {@code Double}, {@code String}, {@code byte[]}
+ * and {@code PersistentDataContainer}.
+ *
+ * @see PersistentDataContainer
+ * @param
the primitive type
+ * @param the complex type
+ */
+public interface PersistentDataType {
+
+ PersistentDataType BYTE = new PrimitivePersistentDataType<>(Byte.class);
+ PersistentDataType SHORT = new PrimitivePersistentDataType<>(Short.class);
+ PersistentDataType INTEGER = new PrimitivePersistentDataType<>(Integer.class);
+ PersistentDataType LONG = new PrimitivePersistentDataType<>(Long.class);
+ PersistentDataType FLOAT = new PrimitivePersistentDataType<>(Float.class);
+ PersistentDataType DOUBLE = new PrimitivePersistentDataType<>(Double.class);
+ PersistentDataType BOOLEAN = new BooleanPersistentDataType();
+ PersistentDataType CHARACTER = new CharacterPersistentDataType();
+ PersistentDataType STRING = new PrimitivePersistentDataType<>(String.class);
+ PersistentDataType BYTE_ARRAY = new PrimitivePersistentDataType<>(byte[].class);
+ PersistentDataType DATA_CONTAINER = new PrimitivePersistentDataType<>(PersistentDataContainer.class);
+
+ /**
+ * Returns the primitive type of a value of this data type.
+ *
+ * @return the primitive type
+ */
+ Class getPrimitiveType();
+
+ /**
+ * Returns the complex type of a value of this data type.
+ *
+ * @return the complex type
+ */
+ Class getComplexType();
+
+ /**
+ * Converts the given complex value to a primitive value.
+ *
+ * @param complex the complex value
+ * @return the primitive value
+ */
+ P toPrimitive(C complex);
+
+ /**
+ * Converts the given primitive value to a complex value.
+ *
+ * @param primitive the primitive value
+ * @return the complex value
+ */
+ C fromPrimitive(P primitive);
+
+ final class PrimitivePersistentDataType implements PersistentDataType
{
+ private final Class
primitiveType;
+
+ PrimitivePersistentDataType(Class
primitiveType) {
+ this.primitiveType = primitiveType;
+ }
+
+ @Override
+ public Class
getPrimitiveType() {
+ return this.primitiveType;
+ }
+
+ @Override
+ public Class
getComplexType() {
+ return this.primitiveType;
+ }
+
+ @Override
+ public P toPrimitive(P complex) {
+ return complex;
+ }
+
+ @Override
+ public P fromPrimitive(P primitive) {
+ return primitive;
+ }
+ }
+
+ final class BooleanPersistentDataType implements PersistentDataType {
+
+ @Override
+ public Class getPrimitiveType() {
+ return Byte.class;
+ }
+
+ @Override
+ public Class getComplexType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public Byte toPrimitive(Boolean complex) {
+ return (byte) (complex ? 1 : 0);
+ }
+
+ @Override
+ public Boolean fromPrimitive(Byte primitive) {
+ return primitive != 0;
+ }
+ }
+
+ final class CharacterPersistentDataType implements PersistentDataType {
+
+ @Override
+ public Class getPrimitiveType() {
+ return Short.class;
+ }
+
+ @Override
+ public Class getComplexType() {
+ return Character.class;
+ }
+
+ @Override
+ public Short toPrimitive(Character complex) {
+ return (short) complex.charValue();
+ }
+
+ @Override
+ public Character fromPrimitive(Short primitive) {
+ return (char) primitive.shortValue();
+ }
+ }
+
+}
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
index 6ff0e70..896bd1c 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
@@ -620,11 +620,31 @@ public void execute(Listener listener, Event event)
}
};
case PLAYER_CHANGED_WORLD:
- return new EventExecutor() {
- public void execute(Listener listener, Event event) {
+ return new EventExecutor()
+ {
+ public void execute(Listener listener, Event event)
+ {
((PlayerListener) listener).onPlayerChangedWorld((PlayerChangedWorldEvent) event);
}
};
+ // Tsunami start - backport plugin messaging
+ case PLAYER_REGISTER_CHANNEL:
+ return new EventExecutor()
+ {
+ public void execute(Listener listener, Event event)
+ {
+ ((PlayerListener) listener).onPlayerRegisterChannel((PlayerRegisterChannelEvent) event);
+ }
+ };
+ case PLAYER_UNREGISTER_CHANNEL:
+ return new EventExecutor()
+ {
+ public void execute(Listener listener, Event event)
+ {
+ ((PlayerListener) listener).onPlayerUnregisterChannel((PlayerUnregisterChannelEvent) event);
+ }
+ };
+ // Tsunami end
// Block Events
case BLOCK_PHYSICS:
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 0000000..2ee5403
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNameTooLongException.java
@@ -0,0 +1,11 @@
+package org.bukkit.plugin.messaging;
+
+/**
+ * Thrown if a Plugin Channel is too long.
+ */
+public class ChannelNameTooLongException extends RuntimeException {
+
+ 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 0000000..56f2e2a
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/ChannelNotRegisteredException.java
@@ -0,0 +1,11 @@
+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(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 0000000..93765fa
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/MessageTooLargeException.java
@@ -0,0 +1,19 @@
+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(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 0000000..8d4480b
--- /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 = 64;
+
+ /**
+ * 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 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 getOutgoingChannels(Plugin plugin);
+
+ /**
+ * Gets a set containing all the incoming plugin channels.
+ *
+ * @return List of all registered incoming plugin channels.
+ */
+ public Set 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 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 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 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 getIncomingChannelRegistrations(Plugin plugin, String channel);
+
+ /**
+ * Checks if the specified plugin message listener registration is valid.
+ *
+ * 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/PluginMessageListener.java b/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java
new file mode 100644
index 0000000..0e197a6
--- /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 0000000..850ba5e
--- /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 0000000..6383166
--- /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.
+ *
+ * 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 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 0000000..2f6fafa
--- /dev/null
+++ b/src/main/java/org/bukkit/plugin/messaging/ReservedChannelException.java
@@ -0,0 +1,11 @@
+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(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 0000000..e90f2e1
--- /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> incomingByChannel = new HashMap>();
+ private final Map> incomingByPlugin = new HashMap>();
+ private final Map> outgoingByChannel = new HashMap>();
+ private final Map> outgoingByPlugin = new HashMap>();
+ private final Object incomingLock = new Object();
+ private final Object outgoingLock = new Object();
+
+ private void addToOutgoing(Plugin plugin, String channel) {
+ synchronized (outgoingLock) {
+ Set plugins = outgoingByChannel.get(channel);
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (plugins == null) {
+ plugins = new HashSet();
+ outgoingByChannel.put(channel, plugins);
+ }
+
+ if (channels == null) {
+ channels = new HashSet();
+ outgoingByPlugin.put(plugin, channels);
+ }
+
+ plugins.add(plugin);
+ channels.add(channel);
+ }
+ }
+
+ private void removeFromOutgoing(Plugin plugin, String channel) {
+ synchronized (outgoingLock) {
+ Set plugins = outgoingByChannel.get(channel);
+ Set 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 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 registrations = incomingByChannel.get(registration.getChannel());
+
+ if (registrations == null) {
+ registrations = new HashSet();
+ 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();
+ 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 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 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 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 getOutgoingChannels() {
+ synchronized (outgoingLock) {
+ Set keys = outgoingByChannel.keySet();
+ return ImmutableSet.copyOf(keys);
+ }
+ }
+
+ public Set getOutgoingChannels(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ synchronized (outgoingLock) {
+ Set channels = outgoingByPlugin.get(plugin);
+
+ if (channels != null) {
+ return ImmutableSet.copyOf(channels);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannels() {
+ synchronized (incomingLock) {
+ Set keys = incomingByChannel.keySet();
+ return ImmutableSet.copyOf(keys);
+ }
+ }
+
+ public Set getIncomingChannels(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ Builder builder = ImmutableSet.builder();
+
+ for (PluginMessageListenerRegistration registration : registrations) {
+ builder.add(registration.getChannel());
+ }
+
+ return builder.build();
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannelRegistrations(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ return ImmutableSet.copyOf(registrations);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannelRegistrations(String channel) {
+ validateChannel(channel);
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByChannel.get(channel);
+
+ if (registrations != null) {
+ return ImmutableSet.copyOf(registrations);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+ }
+
+ public Set getIncomingChannelRegistrations(Plugin plugin, String channel) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null");
+ }
+ validateChannel(channel);
+
+ synchronized (incomingLock) {
+ Set registrations = incomingByPlugin.get(plugin);
+
+ if (registrations != null) {
+ Builder 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 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 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 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 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);
+ }
+}