Last active
July 29, 2017 00:52
-
-
Save dumptruckman/2205a600cd02e800cc2ef2eb73327e51 to your computer and use it in GitHub Desktop.
Type Safe Metadata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.player.PlayerJoinEvent; | |
import org.bukkit.event.player.PlayerQuitEvent; | |
import org.bukkit.plugin.java.JavaPlugin; | |
/** | |
* An example of a pretty dumb plugin that uses Metadata. | |
*/ | |
public class ExamplePlugin extends JavaPlugin implements Listener { | |
public static final MetadataKey<Integer> LOGOFF_COUNT = new MetadataKey<>(Integer.class); | |
// WeakMetadataStore since this is for players. | |
private final MetadataStore playerMetadata = new WeakMetadataStore(); | |
@Override | |
public void onEnable() { | |
getServer().getPluginManager().registerEvents(this, this); | |
} | |
// Inform the player of their logoff count on join. | |
@EventHandler | |
public void playerJoin(PlayerJoinEvent event) { | |
Player player = event.getPlayer(); | |
Metadata metadata = playerMetadata.getMetadata(player); | |
Integer logoffCount = metadata.getOrSet(LOGOFF_COUNT, () -> 0); | |
player.sendMessage("Did you know you've logged off " + logoffCount + " times?"); | |
} | |
// Increment the log off count for the player on quit. | |
@EventHandler | |
public void playerQuit(PlayerQuitEvent event) { | |
Metadata metadata = playerMetadata.getMetadata(event.getPlayer()); | |
Integer logoffCount = metadata.getOrSet(LOGOFF_COUNT, () -> 0); | |
logoffCount += 1; | |
metadata.set(LOGOFF_COUNT, logoffCount); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.function.Supplier; | |
/** | |
* A simple metadata store backed by a HashMap. | |
* | |
* MetadataKey is used as keys in get and set operations in order to ensure types. | |
*/ | |
public class Metadata { | |
private final Map<MetadataKey, Object> metadataMap = new HashMap<>(); | |
/** | |
* Returns the metadata value of a given metadata key or null if no value has been set for that key. | |
* | |
* @param key The key to retrieve the metadata value for. | |
* @return The metadata value for the given key or null if none has been set. | |
*/ | |
@SuppressWarnings("unchecked") | |
public <V> V get(MetadataKey<V> key) { | |
return (V) metadataMap.get(key); | |
} | |
/** | |
* Returns the metadata value of a given metadata key. | |
* | |
* If no value has been set for that key, the defaultSupplier is called to set a value for that key and then | |
* return it. | |
* | |
* @param key The key to retrieve the metadata value for. | |
* @param defaultSupplier A functional supplier for a default metadata value for the given key. | |
* @return The metadata value for the given key or value provided by the supplier if none was present. | |
*/ | |
@SuppressWarnings("unchecked") | |
public <V> V getOrSet(MetadataKey<V> key, Supplier<V> defaultSupplier) { | |
V value = (V) metadataMap.get(key); | |
if (value == null) { | |
value = defaultSupplier.get(); | |
set(key, value); | |
} | |
return value; | |
} | |
/** | |
* Sets the metadata value for a given metadata key. | |
* | |
* If the a value has already been set for the given key, this will replace it with the new given value. | |
* | |
* @param key The key to set the metadata value for. | |
* @param value The new metadata value. | |
*/ | |
public <V> void set(MetadataKey<V> key, V value) { | |
metadataMap.put(key, value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A simple class to be used as a unique key in Metadata in order to retain type information of metadata values. | |
* | |
* @param <T> The type for the metadata value. | |
*/ | |
public class MetadataKey<T> { | |
/** | |
* Creates a MetadataKey for uniquely identifying a metadata value with the given type. | |
* | |
* @param type The type used for the metadata value this MetdataKey will identify. | |
*/ | |
public MetadataKey(Class<T> type) { } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A store for linking Metadata to arbitrary objects. | |
*/ | |
public interface MetadataStore { | |
/** | |
* Returns the Metadata for the given object in this MetadataStore, creating it if it does not exist. | |
* | |
* @param obj The object to get Metadata for. | |
* @return The Metadata for the given object. | |
*/ | |
Metadata getMetadata(Object obj); | |
/** | |
* Returns whether or not the given object has Metadata associated with it in this MetadataStore. | |
* | |
* @param obj The object to check for Metadata. | |
* @return true if the object has Metadata associated with it in this store, false otherwise. | |
*/ | |
boolean hasMetadata(Object obj); | |
/** | |
* Removes any Metadata associated with the given object in this MetadataStore. | |
* | |
* @param obj The object to remove the Metadata for. | |
*/ | |
void clearMetadata(Object obj); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* A MetadataStore using strong references to the object keys. | |
* | |
* This should not be used on players, entities, blocks, worlds, chunks, etc. since those objects can be removed from | |
* the server and having floating references to them can cause weird glitches. | |
*/ | |
public class StrongMetadataStore implements MetadataStore { | |
private final Map<Object, Metadata> objectMetadataMap = new HashMap<>(); | |
@Override | |
public Metadata getMetadata(Object obj) { | |
Metadata result = objectMetadataMap.get(obj); | |
if (result == null) { | |
result = new Metadata(); | |
objectMetadataMap.put(obj, result); | |
} | |
return result; | |
} | |
@Override | |
public boolean hasMetadata(Object obj) { | |
return objectMetadataMap.containsKey(obj); | |
} | |
@Override | |
public void clearMetadata(Object obj) { | |
objectMetadataMap.remove(obj); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Map; | |
import java.util.WeakHashMap; | |
/** | |
* A MetadataStore using weak references to the object keys. | |
* | |
* This type of MetadataStore is good for MC objects that do not exist for the entire life of the server. Examples | |
* of these would be any kind of entities, including players, blocks, worlds, chunks, and so on. Using weak references | |
* to these keys prevent weird "ghost" glitches that can occur when those types of objects get removed from the game. | |
*/ | |
public class WeakMetadataStore implements MetadataStore { | |
private final Map<Object, Metadata> objectMetadataMap = new WeakHashMap<>(); | |
@Override | |
public Metadata getMetadata(Object obj) { | |
Metadata result = objectMetadataMap.get(obj); | |
if (result == null) { | |
result = new Metadata(); | |
objectMetadataMap.put(obj, result); | |
} | |
return result; | |
} | |
@Override | |
public boolean hasMetadata(Object obj) { | |
return objectMetadataMap.containsKey(obj); | |
} | |
@Override | |
public void clearMetadata(Object obj) { | |
objectMetadataMap.remove(obj); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment