Newer
Older
ultramine_bukkit / src / main / java / org / bukkit / metadata / MetadataStoreBase.java
@vlad20012 vlad20012 on 24 Feb 2017 5 KB initial
package org.bukkit.metadata;

import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

public abstract class MetadataStoreBase<T>
{
	private Map<String, Map<Plugin, MetadataValue>> metadataMap = new HashMap<String, Map<Plugin, MetadataValue>>();

	/**
	 * 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.
	 * <p>
	 * 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.
	 * <p>
	 * Bukkit is almost entirely single threaded so locking overhead shouldn't
	 * pose a problem.
	 *
	 * @param subject          The object receiving the metadata.
	 * @param metadataKey      A unique key to identify this metadata.
	 * @param newMetadataValue The metadata value to apply.
	 * @throws IllegalArgumentException If value is null, or the owning plugin
	 *                                  is null
	 * @see MetadataStore#setMetadata(Object, String, MetadataValue)
	 */
	public synchronized void setMetadata(T subject, String metadataKey, MetadataValue newMetadataValue)
	{
		Validate.notNull(newMetadataValue, "Value cannot be null");
		Plugin owningPlugin = newMetadataValue.getOwningPlugin();
		Validate.notNull(owningPlugin, "Plugin cannot be null");
		String key = disambiguate(subject, metadataKey);
		Map<Plugin, MetadataValue> entry = metadataMap.get(key);
		if(entry == null)
		{
			entry = new WeakHashMap<Plugin, MetadataValue>(1);
			metadataMap.put(key, entry);
		}
		entry.put(owningPlugin, newMetadataValue);
	}

	/**
	 * Returns all metadata values attached to an object. If multiple
	 * 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.
	 * @see MetadataStore#getMetadata(Object, String)
	 */
	public synchronized List<MetadataValue> getMetadata(T subject, String metadataKey)
	{
		String key = disambiguate(subject, metadataKey);
		if(metadataMap.containsKey(key))
		{
			Collection<MetadataValue> values = metadataMap.get(key).values();
			return Collections.unmodifiableList(new ArrayList<MetadataValue>(values));
		}
		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 = disambiguate(subject, metadataKey);
		return metadataMap.containsKey(key);
	}

	/**
	 * 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.
	 * @throws IllegalArgumentException If plugin is null
	 * @see MetadataStore#removeMetadata(Object, String,
	 * org.bukkit.plugin.Plugin)
	 */
	public synchronized void removeMetadata(T subject, String metadataKey, Plugin owningPlugin)
	{
		Validate.notNull(owningPlugin, "Plugin cannot be null");
		String key = disambiguate(subject, metadataKey);
		Map<Plugin, MetadataValue> entry = metadataMap.get(key);
		if(entry == null)
		{
			return;
		}

		entry.remove(owningPlugin);
		if(entry.isEmpty())
		{
			metadataMap.remove(key);
		}
	}

	/**
	 * 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.
	 * @throws IllegalArgumentException If plugin is null
	 * @see MetadataStore#invalidateAll(org.bukkit.plugin.Plugin)
	 */
	public synchronized void invalidateAll(Plugin owningPlugin)
	{
		Validate.notNull(owningPlugin, "Plugin cannot be null");
		for(Map<Plugin, MetadataValue> values : metadataMap.values())
		{
			if(values.containsKey(owningPlugin))
			{
				values.get(owningPlugin).invalidate();
			}
		}
	}

	/**
	 * Creates a unique name for the object receiving metadata by combining
	 * unique data from the subject with a metadataKey.
	 * <p>
	 * 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);
}