package tc.oc.commons.bukkit.util; import com.google.common.collect.ForwardingMap; import com.google.common.collect.ImmutableSet; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; import java.util.HashMap; import java.util.Map; /** * {@link java.util.Map} adapter that uses {@link K} keys and guarantees that the map only ever contains valid entries * defined by the {@link #isValid(K)} method, subclasses override that method to define what keys are valid. * Subclass are also responsible for defining the events and their actions when the adapter is enabled. * MapAdapter is a {@link org.bukkit.event.Listener} and registers itself to receive events on behalf of the plugin * passed to the constructor when {@link #enable()} is called. This must be called before using the map. The map * can be unregistered by calling {@link #disable()}. */ public abstract class ListeningMapAdapter<K, V> extends ForwardingMap<K, V> implements Listener { protected final Plugin plugin; protected final Map<K, V> map; protected boolean enabled; protected boolean lazyEnable = true; public ListeningMapAdapter(Plugin plugin) { this(new HashMap<K, V>(), plugin); } public ListeningMapAdapter(Map<K, V> map, Plugin plugin) { this.plugin = plugin; this.map = map; } protected void assertEnabled() { if(!this.enabled) { if(this.lazyEnable) { this.enable(); } else { throw new IllegalStateException("This " + this.getClass().getSimpleName() + " is not enabled"); } } } /** * If the given entry is valid, add it to the map and return any previous value for * that entry, or null if they were previously not in the map. If the entry is not valid, * no change is made to the map and null is returned. */ @Override public V put(K key, V value) { this.assertEnabled(); if(isValid(key)) { return this.map.put(key, value); } else { return null; } } /** * If the entry is a valid new entry */ public abstract boolean isValid(K key); /** * Add an entry to the map for the given key and return any previous value for * that entity, or null if they were previously not in the map. The entry * is added ignoring the checks. */ public V force(K key, V value) { this.assertEnabled(); return this.map.put(key, value); } @Override public void putAll(Map<? extends K, ? extends V> otherMap) { for(Entry<? extends K, ? extends V> entry : otherMap.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } /** * Special case of putAll that does not check if each entry is valid */ public void putAll(ListeningMapAdapter<? extends K, ? extends V> otherMap) { this.assertEnabled(); this.map.putAll(otherMap); } /** * Register to receive events. This must be called before adding any entries to the map. */ public void enable() { if(!this.enabled) { this.plugin.getServer().getPluginManager().registerEvents(this, plugin); this.enabled = true; } } /** * Clear the list and stop listening for events. This map should not be used after this method is called. */ public void disable() { if(this.enabled) { this.clear(); HandlerList.unregisterAll(this); } } /** * Return an immutable copy of this container's {@link #entrySet} that is * safe to iterate over while the container is modified, which tends to * happen unexpectedly from events the container is listening to. */ public ImmutableSet<Entry<K, V>> entrySetCopy() { return ImmutableSet.copyOf(this.entrySet()); } @Override protected Map<K, V> delegate() { return this.map; } }