001 package org.bukkit.metadata;
002
003 import org.apache.commons.lang.Validate;
004 import org.bukkit.plugin.Plugin;
005
006 import java.util.*;
007
008 public abstract class MetadataStoreBase<T> {
009 private Map<String, Map<Plugin, MetadataValue>> metadataMap = new HashMap<String, Map<Plugin, MetadataValue>>();
010
011 /**
012 * Adds a metadata value to an object. Each metadata value is owned by a
013 * specific {@link Plugin}. If a plugin has already added a metadata value
014 * to an object, that value will be replaced with the value of {@code
015 * newMetadataValue}. Multiple plugins can set independent values for the
016 * same {@code metadataKey} without conflict.
017 * <p>
018 * Implementation note: I considered using a {@link
019 * java.util.concurrent.locks.ReadWriteLock} for controlling access to
020 * {@code metadataMap}, but decided that the added overhead wasn't worth
021 * the finer grained access control.
022 * <p>
023 * Bukkit is almost entirely single threaded so locking overhead shouldn't
024 * pose a problem.
025 *
026 * @param subject The object receiving the metadata.
027 * @param metadataKey A unique key to identify this metadata.
028 * @param newMetadataValue The metadata value to apply.
029 * @see MetadataStore#setMetadata(Object, String, MetadataValue)
030 * @throws IllegalArgumentException If value is null, or the owning plugin
031 * is null
032 */
033 public synchronized void setMetadata(T subject, String metadataKey, MetadataValue newMetadataValue) {
034 Validate.notNull(newMetadataValue, "Value cannot be null");
035 Plugin owningPlugin = newMetadataValue.getOwningPlugin();
036 Validate.notNull(owningPlugin, "Plugin cannot be null");
037 String key = disambiguate(subject, metadataKey);
038 Map<Plugin, MetadataValue> entry = metadataMap.get(key);
039 if (entry == null) {
040 entry = new WeakHashMap<Plugin, MetadataValue>(1);
041 metadataMap.put(key, entry);
042 }
043 entry.put(owningPlugin, newMetadataValue);
044 }
045
046 /**
047 * Returns all metadata values attached to an object. If multiple
048 * have attached metadata, each will value will be included.
049 *
050 * @param subject the object being interrogated.
051 * @param metadataKey the unique metadata key being sought.
052 * @return A list of values, one for each plugin that has set the
053 * requested value.
054 * @see MetadataStore#getMetadata(Object, String)
055 */
056 public synchronized List<MetadataValue> getMetadata(T subject, String metadataKey) {
057 String key = disambiguate(subject, metadataKey);
058 if (metadataMap.containsKey(key)) {
059 Collection<MetadataValue> values = metadataMap.get(key).values();
060 return Collections.unmodifiableList(new ArrayList<MetadataValue>(values));
061 } else {
062 return Collections.emptyList();
063 }
064 }
065
066 /**
067 * Tests to see if a metadata attribute has been set on an object.
068 *
069 * @param subject the object upon which the has-metadata test is
070 * performed.
071 * @param metadataKey the unique metadata key being queried.
072 * @return the existence of the metadataKey within subject.
073 */
074 public synchronized boolean hasMetadata(T subject, String metadataKey) {
075 String key = disambiguate(subject, metadataKey);
076 return metadataMap.containsKey(key);
077 }
078
079 /**
080 * Removes a metadata item owned by a plugin from a subject.
081 *
082 * @param subject the object to remove the metadata from.
083 * @param metadataKey the unique metadata key identifying the metadata to
084 * remove.
085 * @param owningPlugin the plugin attempting to remove a metadata item.
086 * @see MetadataStore#removeMetadata(Object, String,
087 * org.bukkit.plugin.Plugin)
088 * @throws IllegalArgumentException If plugin is null
089 */
090 public synchronized void removeMetadata(T subject, String metadataKey, Plugin owningPlugin) {
091 Validate.notNull(owningPlugin, "Plugin cannot be null");
092 String key = disambiguate(subject, metadataKey);
093 Map<Plugin, MetadataValue> entry = metadataMap.get(key);
094 if (entry == null) {
095 return;
096 }
097
098 entry.remove(owningPlugin);
099 if (entry.isEmpty()) {
100 metadataMap.remove(key);
101 }
102 }
103
104 /**
105 * Invalidates all metadata in the metadata store that originates from the
106 * given plugin. Doing this will force each invalidated metadata item to
107 * be recalculated the next time it is accessed.
108 *
109 * @param owningPlugin the plugin requesting the invalidation.
110 * @see MetadataStore#invalidateAll(org.bukkit.plugin.Plugin)
111 * @throws IllegalArgumentException If plugin is null
112 */
113 public synchronized void invalidateAll(Plugin owningPlugin) {
114 Validate.notNull(owningPlugin, "Plugin cannot be null");
115 for (Map<Plugin, MetadataValue> values : metadataMap.values()) {
116 if (values.containsKey(owningPlugin)) {
117 values.get(owningPlugin).invalidate();
118 }
119 }
120 }
121
122 /**
123 * Creates a unique name for the object receiving metadata by combining
124 * unique data from the subject with a metadataKey.
125 * <p>
126 * The name created must be globally unique for the given object and any
127 * two equivalent objects must generate the same unique name. For example,
128 * two Player objects must generate the same string if they represent the
129 * same player, even if the objects would fail a reference equality test.
130 *
131 * @param subject The object for which this key is being generated.
132 * @param metadataKey The name identifying the metadata value.
133 * @return a unique metadata key for the given subject.
134 */
135 protected abstract String disambiguate(T subject, String metadataKey);
136 }