summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorrmichela <deltahat@gmail.com>2011-12-08 00:33:33 -0500
committerErik Broes <erikbroes@grum.nl>2012-02-29 19:16:04 +0100
commitcd732ee3f71ae63711fb776e35caf8e13dbcdc8e (patch)
tree86b895be806bb2788b7fe0fff945df759ade6a71 /src
parentca7aab6f4ffab1a025e7385cafef45b664346d6c (diff)
downloadbukkit-cd732ee3f71ae63711fb776e35caf8e13dbcdc8e.tar
bukkit-cd732ee3f71ae63711fb776e35caf8e13dbcdc8e.tar.gz
bukkit-cd732ee3f71ae63711fb776e35caf8e13dbcdc8e.tar.lz
bukkit-cd732ee3f71ae63711fb776e35caf8e13dbcdc8e.tar.xz
bukkit-cd732ee3f71ae63711fb776e35caf8e13dbcdc8e.zip
[Bleeding] Added a Metadata framework for Entities, Blocks, and Worlds
This metadata implementation has the following features: - All metadata is lazy. Metadata values are not actually computed until another plugin requests them. Memory and CPU are conserved by not computing and storing unnecessary metadata values. - All metadata is cached. Once a metadata value is computed its value is cached in the metadata store to prevent further unnecessary computation. An invalidation mechanism is provided to flush the cache and force recompilation of metadata values. - All metadata is stored in basic data types. Convenience methods in the MetadataValue class allow for the conversion of metadata data types when possible. Restricting metadata to basic data types prevents the accidental linking of large object graphs into metadata. Metadata is persistent across the lifetime of the application and adding large object graphs would damage garbage collector performance. - Metadata access is thread safe. Care has been taken to protect the internal data structures and access them in a thread safe manner. - Metadata is exposed for all objects that descend from Entity, Block, and World. All Entity and World metadata is stored at the Server level and all Block metadata is stored at the World level. - Metadata is NOT keyed on references to original objects - instead metadata is keyed off of unique fields within those objects. Doing this allows metadata to exist for blocks that are in chunks not currently in memory. Additionally, Player objects are keyed off of player name so that Player metadata remains consistent between logins. - Metadata convenience methods have been added to all Entities, Players, Blocks, BlockStates, and World allowing direct access to an individual instance's metadata. - Players and OfflinePlayers share a single metadata store, allowing player metadata to be manipulated regardless of the player's current online status.
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/bukkit/World.java3
-rw-r--r--src/main/java/org/bukkit/block/Block.java3
-rw-r--r--src/main/java/org/bukkit/block/BlockState.java3
-rw-r--r--src/main/java/org/bukkit/entity/Entity.java3
-rw-r--r--src/main/java/org/bukkit/metadata/FixedMetadataValue.java25
-rw-r--r--src/main/java/org/bukkit/metadata/LazyMetadataValue.java158
-rw-r--r--src/main/java/org/bukkit/metadata/MetadataConversionException.java13
-rw-r--r--src/main/java/org/bukkit/metadata/MetadataEvaluationException.java13
-rw-r--r--src/main/java/org/bukkit/metadata/MetadataStore.java52
-rw-r--r--src/main/java/org/bukkit/metadata/MetadataStoreBase.java143
-rw-r--r--src/main/java/org/bukkit/metadata/MetadataValue.java73
-rw-r--r--src/main/java/org/bukkit/metadata/Metadatable.java42
-rw-r--r--src/main/java/org/bukkit/plugin/Plugin.java71
-rw-r--r--src/main/java/org/bukkit/plugin/java/JavaPlugin.java2
-rw-r--r--src/main/java/org/bukkit/util/NumberConversions.java78
-rw-r--r--src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java30
-rw-r--r--src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java135
-rw-r--r--src/test/java/org/bukkit/metadata/MetadataConversionTest.java103
-rw-r--r--src/test/java/org/bukkit/metadata/MetadataStoreTest.java127
-rw-r--r--src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java3
-rw-r--r--src/test/java/org/bukkit/plugin/messaging/TestPlayer.java17
-rw-r--r--src/test/java/org/bukkit/plugin/messaging/TestPlugin.java33
22 files changed, 1058 insertions, 72 deletions
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index 5212f961..1b326a55 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -13,13 +13,14 @@ import org.bukkit.block.Block;
import org.bukkit.entity.*;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.inventory.ItemStack;
+import org.bukkit.metadata.Metadatable;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import org.bukkit.util.Vector;
/**
* Represents a world, which may contain entities, chunks and blocks
*/
-public interface World extends PluginMessageRecipient {
+public interface World extends PluginMessageRecipient, Metadatable {
/**
* Gets the {@link Block} at the given coordinates
diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java
index 7ca3c01b..99bc5630 100644
--- a/src/main/java/org/bukkit/block/Block.java
+++ b/src/main/java/org/bukkit/block/Block.java
@@ -7,6 +7,7 @@ import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
+import org.bukkit.metadata.Metadatable;
/**
* Represents a block. This is a live object, and only one Block may exist for
@@ -14,7 +15,7 @@ import org.bukkit.inventory.ItemStack;
* to your own handling of it; use block.getState() to get a snapshot state of a
* block which will not be modified.
*/
-public interface Block {
+public interface Block extends Metadatable {
/**
* Gets the metadata for this block
diff --git a/src/main/java/org/bukkit/block/BlockState.java b/src/main/java/org/bukkit/block/BlockState.java
index 7e181017..cd5bcfbf 100644
--- a/src/main/java/org/bukkit/block/BlockState.java
+++ b/src/main/java/org/bukkit/block/BlockState.java
@@ -5,6 +5,7 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.material.MaterialData;
+import org.bukkit.metadata.Metadatable;
/**
* Represents a captured state of a block, which will not change automatically.
@@ -14,7 +15,7 @@ import org.bukkit.material.MaterialData;
* the state of the block and you will not know, or they may change the block to
* another type entirely, causing your BlockState to become invalid.
*/
-public interface BlockState {
+public interface BlockState extends Metadatable {
/**
* Gets the block represented by this BlockState
diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
index c8554734..d9d219b3 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.EntityEffect;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.metadata.Metadatable;
import org.bukkit.util.Vector;
import java.util.List;
@@ -14,7 +15,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
/**
* Represents a base entity in the world
*/
-public interface Entity {
+public interface Entity extends Metadatable {
/**
* Gets the entity's current position
diff --git a/src/main/java/org/bukkit/metadata/FixedMetadataValue.java b/src/main/java/org/bukkit/metadata/FixedMetadataValue.java
new file mode 100644
index 00000000..41f89a0e
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/FixedMetadataValue.java
@@ -0,0 +1,25 @@
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.Plugin;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A FixedMetadataValue is a special case metadata item that contains the same value forever after initialization.
+ * Invalidating a FixedMetadataValue has no affect.
+ */
+public class FixedMetadataValue extends LazyMetadataValue {
+ /**
+ * Initializes a FixedMetadataValue with an Object
+ *
+ * @param owningPlugin the {@link Plugin} that created this metadata value.
+ * @param value the value assigned to this metadata value.
+ */
+ public FixedMetadataValue(Plugin owningPlugin, final Object value) {
+ super(owningPlugin, CacheStrategy.CACHE_ETERNALLY, new Callable<Object>() {
+ public Object call() throws Exception {
+ return value;
+ }
+ });
+ }
+}
diff --git a/src/main/java/org/bukkit/metadata/LazyMetadataValue.java b/src/main/java/org/bukkit/metadata/LazyMetadataValue.java
new file mode 100644
index 00000000..cc0ba506
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/LazyMetadataValue.java
@@ -0,0 +1,158 @@
+package org.bukkit.metadata;
+
+import java.lang.ref.SoftReference;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.lang.Validate;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.util.NumberConversions;
+
+/**
+ * The LazyMetadataValue class implements a type of metadata that is not computed until another plugin asks for it.
+ * By making metadata values lazy, no computation is done by the providing plugin until absolutely necessary (if ever).
+ * Additionally, LazyMetadataValue objects cache their values internally unless overridden by a {@link CacheStrategy}
+ * or invalidated at the individual or plugin level. Once invalidated, the LazyMetadataValue will recompute its value
+ * when asked.
+ */
+public class LazyMetadataValue implements MetadataValue {
+ private Callable<Object> lazyValue;
+ private CacheStrategy cacheStrategy;
+ private SoftReference<Object> internalValue = new SoftReference<Object>(null);
+ private Plugin owningPlugin;
+ private static final Object ACTUALLY_NULL = new Object();
+
+ /**
+ * Initialized a LazyMetadataValue object with the default CACHE_AFTER_FIRST_EVAL cache strategy.
+ *
+ * @param owningPlugin the {@link Plugin} that created this metadata value.
+ * @param lazyValue the lazy value assigned to this metadata value.
+ */
+ public LazyMetadataValue(Plugin owningPlugin, Callable<Object> lazyValue) {
+ this(owningPlugin, CacheStrategy.CACHE_AFTER_FIRST_EVAL, lazyValue);
+ }
+
+ /**
+ * Initializes a LazyMetadataValue object with a specific cache strategy.
+ *
+ * @param owningPlugin the {@link Plugin} that created this metadata value.
+ * @param cacheStrategy determines the rules for caching this metadata value.
+ * @param lazyValue the lazy value assigned to this metadata value.
+ */
+ public LazyMetadataValue(Plugin owningPlugin, CacheStrategy cacheStrategy, Callable<Object> lazyValue) {
+ Validate.notNull(owningPlugin, "owningPlugin cannot be null");
+ Validate.notNull(cacheStrategy, "cacheStrategy cannot be null");
+ Validate.notNull(lazyValue, "lazyValue cannot be null");
+
+ this.lazyValue = lazyValue;
+ this.owningPlugin = owningPlugin;
+ this.cacheStrategy = cacheStrategy;
+ }
+
+ public Plugin getOwningPlugin() {
+ return owningPlugin;
+ }
+
+ public Object value() {
+ eval();
+ Object value = internalValue.get();
+ if (value == ACTUALLY_NULL) {
+ return null;
+ }
+ return value;
+ }
+
+ public int asInt() {
+ return NumberConversions.toInt(value());
+ }
+
+ public float asFloat() {
+ return NumberConversions.toFloat(value());
+ }
+
+ public double asDouble() {
+ return NumberConversions.toDouble(value());
+ }
+
+ public long asLong() {
+ return NumberConversions.toLong(value());
+ }
+
+ public short asShort() {
+ return NumberConversions.toShort(value());
+ }
+
+ public byte asByte() {
+ return NumberConversions.toByte(value());
+ }
+
+ public boolean asBoolean() {
+ Object value = value();
+ if (value instanceof Boolean) {
+ return (Boolean) value;
+ }
+
+ if (value instanceof Number) {
+ return ((Number) value).intValue() != 0;
+ }
+
+ if (value instanceof String) {
+ return Boolean.parseBoolean((String) value);
+ }
+
+ return value != null;
+ }
+
+ public String asString() {
+ Object value = value();
+
+ if (value == null) {
+ return "";
+ }
+ return value.toString();
+ }
+
+ /**
+ * Lazily evaluates the value of this metadata item.
+ *
+ * @throws MetadataEvaluationException if computing the metadata value fails.
+ */
+ private synchronized void eval() throws MetadataEvaluationException {
+ if (cacheStrategy == CacheStrategy.NEVER_CACHE || internalValue.get() == null) {
+ try {
+ Object value = lazyValue.call();
+ if (value == null) {
+ value = ACTUALLY_NULL;
+ }
+ internalValue = new SoftReference<Object>(value);
+ } catch (Exception e) {
+ throw new MetadataEvaluationException(e);
+ }
+ }
+ }
+
+ public synchronized void invalidate() {
+ if (cacheStrategy != CacheStrategy.CACHE_ETERNALLY) {
+ internalValue.clear();
+ }
+ }
+
+ /**
+ * Describes possible caching strategies for metadata.
+ */
+ public enum CacheStrategy {
+ /**
+ * Once the metadata value has been evaluated, do not re-evaluate the value until it is manually invalidated.
+ */
+ CACHE_AFTER_FIRST_EVAL,
+
+ /**
+ * Re-evaluate the metadata item every time it is requested
+ */
+ NEVER_CACHE,
+
+ /**
+ * Once the metadata value has been evaluated, do not re-evaluate the value in spite of manual invalidation.
+ */
+ CACHE_ETERNALLY
+ }
+}
diff --git a/src/main/java/org/bukkit/metadata/MetadataConversionException.java b/src/main/java/org/bukkit/metadata/MetadataConversionException.java
new file mode 100644
index 00000000..ff4d1205
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/MetadataConversionException.java
@@ -0,0 +1,13 @@
+package org.bukkit.metadata;
+
+/**
+ * A MetadataConversionException is thrown any time a {@link LazyMetadataValue} attempts to convert a metadata value
+ * to an inappropriate data type.
+ */
+
+@SuppressWarnings("serial")
+public class MetadataConversionException extends RuntimeException {
+ MetadataConversionException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java b/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java
new file mode 100644
index 00000000..6b5ac886
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/MetadataEvaluationException.java
@@ -0,0 +1,13 @@
+package org.bukkit.metadata;
+
+/**
+ * A MetadataEvaluationException is thrown any time a {@link LazyMetadataValue} fails to evaluate its value due to
+ * an exception. The originating exception will be included as this exception's cause.
+ */
+
+@SuppressWarnings("serial")
+public class MetadataEvaluationException extends RuntimeException {
+ MetadataEvaluationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/org/bukkit/metadata/MetadataStore.java b/src/main/java/org/bukkit/metadata/MetadataStore.java
new file mode 100644
index 00000000..0adfa7a0
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/MetadataStore.java
@@ -0,0 +1,52 @@
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.Plugin;
+
+import java.util.List;
+
+public interface MetadataStore<T> {
+ /**
+ * Adds a metadata value to an object.
+ *
+ * @param subject The object receiving the metadata.
+ * @param metadataKey A unique key to identify this metadata.
+ * @param newMetadataValue The metadata value to apply.
+ */
+ public void setMetadata(T subject, String metadataKey, MetadataValue newMetadataValue);
+
+ /**
+ * Returns all metadata values attached to an object. If multiple plugins have attached metadata, each will value
+ * will be included.
+ *
+ * @param subject the object being interrogated.
+ * @param metadataKey the unique metadata key being sought.
+ * @return A list of values, one for each plugin that has set the requested value.
+ */
+ public List<MetadataValue> getMetadata(T subject, String metadataKey);
+
+ /**
+ * Tests to see if a metadata attribute has been set on an object.
+ *
+ * @param subject the object upon which the has-metadata test is performed.
+ * @param metadataKey the unique metadata key being queried.
+ * @return the existence of the metadataKey within subject.
+ */
+ public boolean hasMetadata(T subject, String metadataKey);
+
+ /**
+ * Removes a metadata item owned by a plugin from a subject.
+ *
+ * @param subject the object to remove the metadata from.
+ * @param metadataKey the unique metadata key identifying the metadata to remove.
+ * @param owningPlugin the plugin attempting to remove a metadata item.
+ */
+ public void removeMetadata(T subject, String metadataKey, Plugin owningPlugin);
+
+ /**
+ * Invalidates all metadata in the metadata store that originates from the given plugin. Doing this will force
+ * each invalidated metadata item to be recalculated the next time it is accessed.
+ *
+ * @param owningPlugin the plugin requesting the invalidation.
+ */
+ public void invalidateAll(Plugin owningPlugin);
+}
diff --git a/src/main/java/org/bukkit/metadata/MetadataStoreBase.java b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java
new file mode 100644
index 00000000..0c384abe
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java
@@ -0,0 +1,143 @@
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.Plugin;
+
+import java.util.*;
+
+public abstract class MetadataStoreBase<T> {
+ private Map<String, List<MetadataValue>> metadataMap = new HashMap<String, List<MetadataValue>>();
+ private WeakHashMap<T, String> disambiguationCache = new WeakHashMap<T, String>();
+
+ /**
+ * Adds a metadata value to an object. Each metadata value is owned by a specific{@link Plugin}.
+ * If a plugin has already added a metadata value to an object, that value
+ * will be replaced with the value of {@code newMetadataValue}. Multiple plugins can set independent values for
+ * the same {@code metadataKey} without conflict.
+ *
+ * Implementation note: I considered using a {@link java.util.concurrent.locks.ReadWriteLock} for controlling
+ * access to {@code metadataMap}, but decided that the added overhead wasn't worth the finer grained access control.
+ * Bukkit is almost entirely single threaded so locking overhead shouldn't pose a problem.
+ *
+ * @see MetadataStore#setMetadata(Object, String, MetadataValue)
+ * @param subject The object receiving the metadata.
+ * @param metadataKey A unique key to identify this metadata.
+ * @param newMetadataValue The metadata value to apply.
+ */
+ public synchronized void setMetadata(T subject, String metadataKey, MetadataValue newMetadataValue) {
+ String key = cachedDisambiguate(subject, metadataKey);
+ if (!metadataMap.containsKey(key)) {
+ metadataMap.put(key, new ArrayList<MetadataValue>());
+ }
+ // we now have a list of subject's metadata for the given metadata key. If newMetadataValue's owningPlugin
+ // is found in this list, replace the value rather than add a new one.
+ List<MetadataValue> metadataList = metadataMap.get(key);
+ for (int i = 0; i < metadataList.size(); i++) {
+ if (metadataList.get(i).getOwningPlugin().equals(newMetadataValue.getOwningPlugin())) {
+ metadataList.set(i, newMetadataValue);
+ return;
+ }
+ }
+ // we didn't find a duplicate...add the new metadata value
+ metadataList.add(newMetadataValue);
+ }
+
+ /**
+ * Returns all metadata values attached to an object. If multiple plugins have attached metadata, each will value
+ * will be included.
+ *
+ * @see MetadataStore#getMetadata(Object, String)
+ * @param subject the object being interrogated.
+ * @param metadataKey the unique metadata key being sought.
+ * @return A list of values, one for each plugin that has set the requested value.
+ */
+ public synchronized List<MetadataValue> getMetadata(T subject, String metadataKey) {
+ String key = cachedDisambiguate(subject, metadataKey);
+ if (metadataMap.containsKey(key)) {
+ return Collections.unmodifiableList(metadataMap.get(key));
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Tests to see if a metadata attribute has been set on an object.
+ *
+ * @param subject the object upon which the has-metadata test is performed.
+ * @param metadataKey the unique metadata key being queried.
+ * @return the existence of the metadataKey within subject.
+ */
+ public synchronized boolean hasMetadata(T subject, String metadataKey) {
+ String key = cachedDisambiguate(subject, metadataKey);
+ return metadataMap.containsKey(key);
+ }
+
+ /**
+ * Removes a metadata item owned by a plugin from a subject.
+ *
+ * @see MetadataStore#removeMetadata(Object, String, org.bukkit.plugin.Plugin)
+ * @param subject the object to remove the metadata from.
+ * @param metadataKey the unique metadata key identifying the metadata to remove.
+ * @param owningPlugin the plugin attempting to remove a metadata item.
+ */
+ public synchronized void removeMetadata(T subject, String metadataKey, Plugin owningPlugin) {
+ String key = cachedDisambiguate(subject, metadataKey);
+ List<MetadataValue> metadataList = metadataMap.get(key);
+ for (int i = 0; i < metadataList.size(); i++) {
+ if (metadataList.get(i).getOwningPlugin().equals(owningPlugin)) {
+ metadataList.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Invalidates all metadata in the metadata store that originates from the given plugin. Doing this will force
+ * each invalidated metadata item to be recalculated the next time it is accessed.
+ *
+ * @see MetadataStore#invalidateAll(org.bukkit.plugin.Plugin)
+ * @param owningPlugin the plugin requesting the invalidation.
+ */
+ public synchronized void invalidateAll(Plugin owningPlugin) {
+ if(owningPlugin == null) {
+ throw new IllegalArgumentException("owningPlugin cannot be null");
+ }
+
+ for (List<MetadataValue> values : metadataMap.values()) {
+ for (MetadataValue value : values) {
+ if (value.getOwningPlugin().equals(owningPlugin)) {
+ value.invalidate();
+ }
+ }
+ }
+ }
+
+ /**
+ * Caches the results of calls to {@link MetadataStoreBase#disambiguate(Object, String)} in a {@link WeakHashMap}. Doing so maintains a
+ * <a href="http://www.codeinstructions.com/2008/09/weakhashmap-is-not-cache-understanding.html">canonical list</a>
+ * of disambiguation strings for objects in memory. When those objects are garbage collected, the disambiguation string
+ * in the list is aggressively garbage collected as well.
+ * @param subject The object for which this key is being generated.
+ * @param metadataKey The name identifying the metadata value.
+ * @return a unique metadata key for the given subject.
+ */
+ private String cachedDisambiguate(T subject, String metadataKey) {
+ if (disambiguationCache.containsKey(subject)) {
+ return disambiguationCache.get(subject);
+ } else {
+ String disambiguation = disambiguate(subject, metadataKey);
+ disambiguationCache.put(subject, disambiguation);
+ return disambiguation;
+ }
+ }
+
+ /**
+ * Creates a unique name for the object receiving metadata by combining unique data from the subject with a metadataKey.
+ * The name created must be globally unique for the given object and any two equivalent objects must generate the
+ * same unique name. For example, two Player objects must generate the same string if they represent the same player,
+ * even if the objects would fail a reference equality test.
+ *
+ * @param subject The object for which this key is being generated.
+ * @param metadataKey The name identifying the metadata value.
+ * @return a unique metadata key for the given subject.
+ */
+ protected abstract String disambiguate(T subject, String metadataKey);
+}
diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java
new file mode 100644
index 00000000..761d1ac9
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/MetadataValue.java
@@ -0,0 +1,73 @@
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.Plugin;
+
+public interface MetadataValue {
+
+ /**
+ * Fetches the value of this metadata item.
+ *
+ * @return the metadata value.
+ */
+ public Object value();
+
+ /**
+ * Attempts to convert the value of this metadata item into an int.
+ * @return the value as an int.
+ */
+ public int asInt();
+
+ /**
+ * Attempts to convert the value of this metadata item into a float.
+ * @return the value as a float.
+ */
+ public float asFloat();
+
+ /**
+ * Attempts to convert the value of this metadata item into a double.
+ * @return the value as a double.
+ */
+ public double asDouble();
+
+ /**
+ * Attempts to convert the value of this metadata item into a long.
+ * @return the value as a long.
+ */
+ public long asLong();
+
+ /**
+ * Attempts to convert the value of this metadata item into a short.
+ * @return the value as a short.
+ */
+ public short asShort();
+
+ /**
+ * Attempts to convert the value of this metadata item into a byte.
+ * @return the value as a byte.
+ */
+ public byte asByte();
+
+ /**
+ * Attempts to convert the value of this metadata item into a boolean.
+ * @return the value as a boolean.
+ */
+ public boolean asBoolean();
+
+ /**
+ * Attempts to convert the value of this metadata item into a string.
+ * @return the value as a string.
+ */
+ public String asString();
+
+ /**
+ * Returns the {@link Plugin} that created this metadata item.
+ *
+ * @return the plugin that owns this metadata value.
+ */
+ public Plugin getOwningPlugin();
+
+ /**
+ * Invalidates this metadata item, forcing it to recompute when next accessed.
+ */
+ public void invalidate();
+}
diff --git a/src/main/java/org/bukkit/metadata/Metadatable.java b/src/main/java/org/bukkit/metadata/Metadatable.java
new file mode 100644
index 00000000..956a1960
--- /dev/null
+++ b/src/main/java/org/bukkit/metadata/Metadatable.java
@@ -0,0 +1,42 @@
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.Plugin;
+
+import java.util.List;
+
+/**
+ * This interface is implemented by all objects that can provide metadata about themselves.
+ */
+public interface Metadatable {
+ /**
+ * Sets a metadata value in the implementing object's metadata store.
+ *
+ * @param metadataKey A unique key to identify this metadata.
+ * @param newMetadataValue The metadata value to apply.
+ */
+ public void setMetadata(String metadataKey, MetadataValue newMetadataValue);
+
+ /**
+ * Returns a list of previously set metadata values from the implementing object's metadata store.
+ *
+ * @param metadataKey the unique metadata key being sought.
+ * @return A list of values, one for each plugin that has set the requested value.
+ */
+ public List<MetadataValue> getMetadata(String metadataKey);
+
+ /**
+ * Tests to see whether the implementing object contains the given metadata value in its metadata store.
+ *
+ * @param metadataKey the unique metadata key being queried.
+ * @return the existence of the metadataKey within subject.
+ */
+ public boolean hasMetadata(String metadataKey);
+
+ /**
+ * Removes the given metadata value from the implementing object's metadata store.
+ *
+ * @param metadataKey the unique metadata key identifying the metadata to remove.
+ * @param owningPlugin This plugin's metadata value will be removed. All other values will be left untouched.
+ */
+ public void removeMetadata(String metadataKey, Plugin owningPlugin);
+}
diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java
index 450252d8..97c882aa 100644
--- a/src/main/java/org/bukkit/plugin/Plugin.java
+++ b/src/main/java/org/bukkit/plugin/Plugin.java
@@ -14,22 +14,21 @@ import com.avaje.ebean.EbeanServer;
/**
* Represents a Plugin
*/
-public interface Plugin extends CommandExecutor {
-
+public abstract class Plugin implements CommandExecutor {
/**
* Returns the folder that the plugin data's files are located in. The
* folder may not yet exist.
*
* @return The folder
*/
- public File getDataFolder();
+ public abstract File getDataFolder();
/**
* Returns the plugin.yaml file containing the details for this plugin
*
* @return Contents of the plugin.yaml file
*/
- public PluginDescriptionFile getDescription();
+ public abstract PluginDescriptionFile getDescription();
/**
* Gets a {@link FileConfiguration} for this plugin, read through "config.yml"
@@ -39,7 +38,7 @@ public interface Plugin extends CommandExecutor {
*
* @return Plugin configuration
*/
- public FileConfiguration getConfig();
+ public abstract FileConfiguration getConfig();
/**
* Gets an embedded resource in this plugin
@@ -47,18 +46,18 @@ public interface Plugin extends CommandExecutor {
* @param filename Filename of the resource
* @return File if found, otherwise null
*/
- public InputStream getResource(String filename);
+ public abstract InputStream getResource(String filename);
/**
* Saves the {@link FileConfiguration} retrievable by {@link #getConfig()}.
*/
- public void saveConfig();
+ public abstract void saveConfig();
/**
* Saves the raw contents of the default config.yml file to the location retrievable by {@link #getConfig()}.
* If there is no default config.yml embedded in the plugin, an empty config.yml file is saved.
*/
- public void saveDefaultConfig();
+ public abstract void saveDefaultConfig();
/**
* Saves the raw contents of any resource embedded with a plugin's .jar file assuming it can be found using
@@ -69,70 +68,70 @@ public interface Plugin extends CommandExecutor {
* @param replace if true, the embedded resource will overwrite the contents of an existing file.
* @throws IllegalArgumentException if the resource path is null, empty, or points to a nonexistent resource.
*/
- public void saveResource(String resourcePath, boolean replace);
+ public abstract void saveResource(String resourcePath, boolean replace);
/**
* Discards any data in {@link #getConfig()} and reloads from disk.
*/
- public void reloadConfig();
+ public abstract void reloadConfig();
/**
* Gets the associated PluginLoader responsible for this plugin
*
* @return PluginLoader that controls this plugin
*/
- public PluginLoader getPluginLoader();
+ public abstract PluginLoader getPluginLoader();
/**
* Returns the Server instance currently running this plugin
*
* @return Server running this plugin
*/
- public Server getServer();
+ public abstract Server getServer();
/**
* Returns a value indicating whether or not this plugin is currently enabled
*
* @return true if this plugin is enabled, otherwise false
*/
- public boolean isEnabled();
+ public abstract boolean isEnabled();
/**
* Called when this plugin is disabled
*/
- public void onDisable();
+ public abstract void onDisable();
/**
* Called after a plugin is loaded but before it has been enabled.
* When mulitple plugins are loaded, the onLoad() for all plugins is called before any onEnable() is called.
*/
- public void onLoad();
+ public abstract void onLoad();
/**
* Called when this plugin is enabled
*/
- public void onEnable();
+ public abstract void onEnable();
/**
* Simple boolean if we can still nag to the logs about things
*
* @return boolean whether we can nag
*/
- public boolean isNaggable();
+ public abstract boolean isNaggable();
/**
* Set naggable state
*
* @param canNag is this plugin still naggable?
*/
- public void setNaggable(boolean canNag);
+ public abstract void setNaggable(boolean canNag);
/**
* Gets the {@link EbeanServer} tied to this plugin
*
* @return Ebean server instance
*/
- public EbeanServer getDatabase();
+ public abstract EbeanServer getDatabase();
/**
* Gets a {@link ChunkGenerator} for use in a default world, as specified in the server configuration
@@ -141,7 +140,7 @@ public interface Plugin extends CommandExecutor {
* @param id Unique ID, if any, that was specified to indicate which generator was requested
* @return ChunkGenerator for use in the default world generation
*/
- public ChunkGenerator getDefaultWorldGenerator(String worldName, String id);
+ public abstract ChunkGenerator getDefaultWorldGenerator(String worldName, String id);
/**
* Returns the primary logger associated with this server instance. The returned logger automatically
@@ -149,5 +148,35 @@ public interface Plugin extends CommandExecutor {
*
* @return Logger associated with this server
*/
- public Logger getLogger();
+ public abstract Logger getLogger();
+
+ /**
+ * Returns the name of the plugin.
+ *
+ * This should return the bare name of the plugin and should be used for comparison.
+ *
+ * @return name of the plugin
+ */
+ public String getName() {
+ return getDescription().getName();
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Plugin)) {
+ return false;
+ }
+ return getName().equals(((Plugin) obj).getName());
+ }
}
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
index 10b19e91..6cc7d9db 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
@@ -34,7 +34,7 @@ import com.avaje.ebeaninternal.server.ddl.DdlGenerator;
/**
* Represents a Java plugin
*/
-public abstract class JavaPlugin implements Plugin {
+public abstract class JavaPlugin extends Plugin {
private boolean isEnabled = false;
private boolean initialized = false;
private PluginLoader loader = null;
diff --git a/src/main/java/org/bukkit/util/NumberConversions.java b/src/main/java/org/bukkit/util/NumberConversions.java
index f70cc314..9c0b132f 100644
--- a/src/main/java/org/bukkit/util/NumberConversions.java
+++ b/src/main/java/org/bukkit/util/NumberConversions.java
@@ -9,84 +9,78 @@ public final class NumberConversions {
public static int toInt(Object object) {
if (object instanceof Number) {
return ((Number) object).intValue();
- } else {
- int result = 0;
-
- try {
- result = Integer.parseInt(object.toString());
- } catch (NumberFormatException ex) {}
+ }
- return result;
+ try {
+ return Integer.valueOf(object.toString());
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
}
+ return 0;
}
public static float toFloat(Object object) {
if (object instanceof Number) {
return ((Number) object).floatValue();
- } else {
- float result = 0;
-
- try {
- result = Float.parseFloat(object.toString());
- } catch (NumberFormatException ex) {}
+ }
- return result;
+ try {
+ return Float.valueOf(object.toString());
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
}
+ return 0;
}
public static double toDouble(Object object) {
if (object instanceof Number) {
return ((Number) object).doubleValue();
- } else {
- double result = 0;
-
- try {
- result = Double.parseDouble(object.toString());
- } catch (NumberFormatException ex) {}
+ }
- return result;
+ try {
+ return Double.valueOf(object.toString());
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
}
+ return 0;
}
public static long toLong(Object object) {
if (object instanceof Number) {
return ((Number) object).longValue();
- } else {
- long result = 0;
-
- try {
- result = Long.parseLong(object.toString());
- } catch (NumberFormatException ex) {}
+ }
- return result;
+ try {
+ return Long.valueOf(object.toString());
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
}
+ return 0;
}
public static short toShort(Object object) {
if (object instanceof Number) {
return ((Number) object).shortValue();
- } else {
- short result = 0;
-
- try {
- result = Short.parseShort(object.toString());
- } catch (NumberFormatException ex) {}
+ }
- return result;
+ try {
+ return Short.valueOf(object.toString());
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
}
+ return 0;
}
public static byte toByte(Object object) {
if (object instanceof Number) {
return ((Number) object).byteValue();
- } else {
- byte result = 0;
-
- try {
- result = Byte.parseByte(object.toString());
- } catch (NumberFormatException ex) {}
+ }
- return result;
+ try {
+ return Byte.valueOf(object.toString());
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
}
+ return 0;
}
}
diff --git a/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java b/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java
new file mode 100644
index 00000000..0844f975
--- /dev/null
+++ b/src/test/java/org/bukkit/metadata/FixedMetadataValueTest.java
@@ -0,0 +1,30 @@
+package org.bukkit.metadata;
+
+import static org.junit.Assert.assertEquals;
+
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.messaging.TestPlugin;
+import org.junit.Test;
+
+public class FixedMetadataValueTest {
+ private Plugin plugin = new TestPlugin("X");
+ private FixedMetadataValue subject;
+
+ private void valueEquals(Object value) {
+ subject = new FixedMetadataValue(plugin, value);
+ assertEquals(value, subject.value());
+ }
+
+ @Test
+ public void testTypes() {
+ valueEquals(10);
+ valueEquals(0.1);
+ valueEquals("TEN");
+ valueEquals(true);
+ valueEquals(null);
+ valueEquals((float) 10.5);
+ valueEquals((long) 10);
+ valueEquals((short) 10);
+ valueEquals((byte) 10);
+ }
+}
diff --git a/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java b/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java
new file mode 100644
index 00000000..ece6a0f4
--- /dev/null
+++ b/src/test/java/org/bukkit/metadata/LazyMetadataValueTest.java
@@ -0,0 +1,135 @@
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.messaging.TestPlugin;
+import org.junit.Test;
+
+import java.util.concurrent.Callable;
+
+import static org.junit.Assert.*;
+
+public class LazyMetadataValueTest {
+ private LazyMetadataValue subject;
+ private TestPlugin plugin = new TestPlugin("x");
+
+ @Test
+ public void testLazyInt() {
+ int value = 10;
+ subject = makeSimpleCallable(value);
+
+ assertEquals(value, subject.value());
+ }
+
+ @Test
+ public void testLazyDouble() {
+ double value = 10.5;
+ subject = makeSimpleCallable(value);
+
+ assertEquals(value, (Double)subject.value(), 0.01);
+ }
+
+ @Test
+ public void testLazyString() {
+ String value = "TEN";
+ subject = makeSimpleCallable(value);
+
+ assertEquals(value, subject.value());
+ }
+
+ @Test
+ public void testLazyBoolean() {
+ boolean value = false;
+ subject = makeSimpleCallable(value);
+
+ assertEquals(value, subject.value());
+ }
+
+ @Test(expected=MetadataEvaluationException.class)
+ public void testEvalException() {
+ subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.CACHE_AFTER_FIRST_EVAL, new Callable<Object>() {
+ public Object call() throws Exception {
+ throw new RuntimeException("Gotcha!");
+ }
+ });
+ subject.value();
+ }
+
+ @Test
+ public void testCacheStrategyCacheAfterFirstEval() {
+ final Counter counter = new Counter();
+ final int value = 10;
+ subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.CACHE_AFTER_FIRST_EVAL, new Callable<Object>() {
+ public Object call() throws Exception {
+ counter.increment();
+ return value;
+ }
+ });
+
+ subject.value();
+ subject.value();
+ assertEquals(value, subject.value());
+ assertEquals(1, counter.value());
+
+ subject.invalidate();
+ subject.value();
+ assertEquals(2, counter.value());
+ }
+
+ @Test
+ public void testCacheStrategyNeverCache() {
+ final Counter counter = new Counter();
+ final int value = 10;
+ subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.NEVER_CACHE, new Callable<Object>() {
+ public Object call() throws Exception {
+ counter.increment();
+ return value;
+ }
+ });
+
+ subject.value();
+ subject.value();
+ assertEquals(value, subject.value());
+ assertEquals(3, counter.value());
+ }
+
+ @Test
+ public void testCacheStrategyEternally() {
+ final Counter counter = new Counter();
+ final int value = 10;
+ subject = new LazyMetadataValue(plugin, LazyMetadataValue.CacheStrategy.CACHE_ETERNALLY, new Callable<Object>() {
+ public Object call() throws Exception {
+ counter.increment();
+ return value;
+ }
+ });
+
+ subject.value();
+ subject.value();
+ assertEquals(value, subject.value());
+ assertEquals(1, counter.value());
+
+ subject.invalidate();
+ subject.value();
+ assertEquals(value, subject.value());
+ assertEquals(1, counter.value());
+ }
+
+ private LazyMetadataValue makeSimpleCallable(final Object value) {
+ return new LazyMetadataValue(plugin, new Callable<Object>() {
+ public Object call() throws Exception {
+ return value;
+ }
+ });
+ }
+
+ private class Counter {
+ private int c = 0;
+
+ public void increment() {
+ c++;
+ }
+
+ public int value() {
+ return c;
+ }
+ }
+}
diff --git a/src/test/java/org/bukkit/metadata/MetadataConversionTest.java b/src/test/java/org/bukkit/metadata/MetadataConversionTest.java
new file mode 100644
index 00000000..428768ad
--- /dev/null
+++ b/src/test/java/org/bukkit/metadata/MetadataConversionTest.java
@@ -0,0 +1,103 @@
+// Copyright (C) 2011 Ryan Michela
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package org.bukkit.metadata;
+
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.messaging.TestPlugin;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ */
+public class MetadataConversionTest {
+ private Plugin plugin = new TestPlugin("x");
+ private FixedMetadataValue subject;
+
+ private void setSubject(Object value) {
+ subject = new FixedMetadataValue(plugin, value);
+ }
+
+ @Test
+ public void testFromInt() {
+ setSubject(10);
+
+ assertEquals(10, subject.asInt());
+ assertEquals(10, subject.asFloat(), 0.000001);
+ assertEquals(10, subject.asDouble(), 0.000001);
+ assertEquals(10, subject.asLong());
+ assertEquals(10, subject.asShort());
+ assertEquals(10, subject.asByte());
+ assertEquals(true, subject.asBoolean());
+ assertEquals("10", subject.asString());
+ }
+
+ @Test
+ public void testFromFloat() {
+ setSubject(10.5);
+
+ assertEquals(10, subject.asInt());
+ assertEquals(10.5, subject.asFloat(), 0.000001);
+ assertEquals(10.5, subject.asDouble(), 0.000001);
+ assertEquals(10, subject.asLong());
+ assertEquals(10, subject.asShort());
+ assertEquals(10, subject.asByte());
+ assertEquals(true, subject.asBoolean());
+ assertEquals("10.5", subject.asString());
+ }
+
+ @Test
+ public void testFromNumericString() {
+ setSubject("10");
+
+ assertEquals(10, subject.asInt());
+ assertEquals(10, subject.asFloat(), 0.000001);
+ assertEquals(10, subject.asDouble(), 0.000001);
+ assertEquals(10, subject.asLong());
+ assertEquals(10, subject.asShort());
+ assertEquals(10, subject.asByte());
+ assertEquals(false, subject.asBoolean());
+ assertEquals("10", subject.asString());
+ }
+
+ @Test
+ public void testFromNonNumericString() {
+ setSubject("true");
+
+ assertEquals(0, subject.asInt());
+ assertEquals(0, subject.asFloat(), 0.000001);
+ assertEquals(0, subject.asDouble(), 0.000001);
+ assertEquals(0, subject.asLong());
+ assertEquals(0, subject.asShort());
+ assertEquals(0, subject.asByte());
+ assertEquals(true, subject.asBoolean());
+ assertEquals("true", subject.asString());
+ }
+
+ @Test
+ public void testFromNull() {
+ setSubject(null);
+
+ assertEquals(0, subject.asInt());
+ assertEquals(0, subject.asFloat(), 0.000001);
+ assertEquals(0, subject.asDouble(), 0.000001);
+ assertEquals(0, subject.asLong());
+ assertEquals(0, subject.asShort());
+ assertEquals(0, subject.asByte());
+ assertEquals(false, subject.asBoolean());
+ assertEquals("", subject.asString());
+ }
+}
diff --git a/src/test/java/org/bukkit/metadata/MetadataStoreTest.java b/src/test/java/org/bukkit/metadata/MetadataStoreTest.java
new file mode 100644
index 00000000..98a96423
--- /dev/null
+++ b/src/test/java/org/bukkit/metadata/MetadataStoreTest.java
@@ -0,0 +1,127 @@
+package org.bukkit.metadata;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.messaging.TestPlugin;
+import org.junit.Test;
+
+public class MetadataStoreTest {
+ private Plugin pluginX = new TestPlugin("x");
+ private Plugin pluginY = new TestPlugin("y");
+
+ StringMetadataStore subject = new StringMetadataStore();
+
+ @Test
+ public void testMetadataStore() {
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10));
+
+ assertTrue(subject.hasMetadata("subject", "key"));
+ List<MetadataValue> values = subject.getMetadata("subject", "key");
+ assertEquals(10, values.get(0).value());
+ }
+
+ @Test
+ public void testMetadataNotPresent() {
+ assertFalse(subject.hasMetadata("subject", "key"));
+ List<MetadataValue> values = subject.getMetadata("subject", "key");
+ assertTrue(values.isEmpty());
+ }
+
+ @Test
+ public void testInvalidateAll() {
+ final Counter counter = new Counter();
+
+ subject.setMetadata("subject", "key", new LazyMetadataValue(pluginX, new Callable<Object>() {
+ public Object call() throws Exception {
+ counter.increment();
+ return 10;
+ }
+ }));
+
+ assertTrue(subject.hasMetadata("subject", "key"));
+ subject.getMetadata("subject", "key").get(0).value();
+ subject.invalidateAll(pluginX);
+ subject.getMetadata("subject", "key").get(0).value();
+ assertEquals(2, counter.value());
+ }
+
+ @Test
+ public void testInvalidateAllButActuallyNothing() {
+ final Counter counter = new Counter();
+
+ subject.setMetadata("subject", "key", new LazyMetadataValue(pluginX, new Callable<Object>() {
+ public Object call() throws Exception {
+ counter.increment();
+ return 10;
+ }
+ }));
+
+ assertTrue(subject.hasMetadata("subject", "key"));
+ subject.getMetadata("subject", "key").get(0).value();
+ subject.invalidateAll(pluginY);
+ subject.getMetadata("subject", "key").get(0).value();
+ assertEquals(1, counter.value());
+ }
+
+ @Test
+ public void testMetadataReplace() {
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10));
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginY, 10));
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 20));
+
+ for (MetadataValue mv : subject.getMetadata("subject", "key")) {
+ if (mv.getOwningPlugin().equals(pluginX)) {
+ assertEquals(20, mv.value());
+ }
+ if (mv.getOwningPlugin().equals(pluginY)) {
+ assertEquals(10, mv.value());
+ }
+ }
+ }
+
+ @Test
+ public void testMetadataRemove() {
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10));
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginY, 20));
+ subject.removeMetadata("subject", "key", pluginX);
+
+ assertTrue(subject.hasMetadata("subject", "key"));
+ assertEquals(1, subject.getMetadata("subject", "key").size());
+ assertEquals(20, subject.getMetadata("subject", "key").get(0).value());
+ }
+
+ @Test
+ public void testMetadataRemoveForNonExistingPlugin() {
+ subject.setMetadata("subject", "key", new FixedMetadataValue(pluginX, 10));
+ subject.removeMetadata("subject", "key", pluginY);
+
+ assertTrue(subject.hasMetadata("subject", "key"));
+ assertEquals(1, subject.getMetadata("subject", "key").size());
+ assertEquals(10, subject.getMetadata("subject", "key").get(0).value());
+ }
+
+ private class StringMetadataStore extends MetadataStoreBase<String> implements MetadataStore<String> {
+ @Override
+ protected String disambiguate(String subject, String metadataKey) {
+ return subject + ":" + metadataKey;
+ }
+ }
+
+ private class Counter {
+ int c = 0;
+
+ public void increment() {
+ c++;
+ }
+
+ public int value() {
+ return c;
+ }
+ }
+}
diff --git a/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java b/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java
index 52e4a0fb..ee3d8acf 100644
--- a/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java
+++ b/src/test/java/org/bukkit/plugin/messaging/StandardMessengerTest.java
@@ -11,8 +11,9 @@ public class StandardMessengerTest {
return new StandardMessenger();
}
+ private int count = 0;
public TestPlugin getPlugin() {
- return new TestPlugin();
+ return new TestPlugin("" + count++);
}
@Test
diff --git a/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java b/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java
index 4ecd9745..da6b7d81 100644
--- a/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java
+++ b/src/test/java/org/bukkit/plugin/messaging/TestPlayer.java
@@ -22,6 +22,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.map.MapView;
+import org.bukkit.metadata.MetadataValue;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
@@ -658,4 +659,20 @@ public class TestPlayer implements Player {
public EntityType getType() {
return EntityType.PLAYER;
}
+
+ public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public List<MetadataValue> getMetadata(String metadataKey) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public boolean hasMetadata(String metadataKey) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void removeMetadata(String metadataKey, Plugin owningPlugin) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
}
diff --git a/src/test/java/org/bukkit/plugin/messaging/TestPlugin.java b/src/test/java/org/bukkit/plugin/messaging/TestPlugin.java
index a03581c3..f14e2e75 100644
--- a/src/test/java/org/bukkit/plugin/messaging/TestPlugin.java
+++ b/src/test/java/org/bukkit/plugin/messaging/TestPlugin.java
@@ -1,8 +1,8 @@
package org.bukkit.plugin.messaging;
-import com.avaje.ebean.EbeanServer;
import java.io.File;
import java.io.InputStream;
+
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@@ -13,19 +13,31 @@ import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoader;
import org.bukkit.plugin.PluginLogger;
-public class TestPlugin implements Plugin {
+import com.avaje.ebean.EbeanServer;
+
+public class TestPlugin extends Plugin {
private boolean enabled = true;
+ final private String pluginName;
+
+ public TestPlugin(String pluginName) {
+ this.pluginName = pluginName;
+ }
+
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
+ public String getName() {
+ return pluginName;
+ }
+
public File getDataFolder() {
throw new UnsupportedOperationException("Not supported.");
}
public PluginDescriptionFile getDescription() {
- throw new UnsupportedOperationException("Not supported.");
+ return new PluginDescriptionFile(pluginName, "1.0", "test.test");
}
public FileConfiguration getConfig() {
@@ -100,4 +112,19 @@ public class TestPlugin implements Plugin {
throw new UnsupportedOperationException("Not supported.");
}
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ return getName().equals(((TestPlugin) obj).getName());
+ }
}