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    }