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 }