Skip to content

Instantly share code, notes, and snippets.

@dumptruckman
Last active July 29, 2017 00:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dumptruckman/2205a600cd02e800cc2ef2eb73327e51 to your computer and use it in GitHub Desktop.
Save dumptruckman/2205a600cd02e800cc2ef2eb73327e51 to your computer and use it in GitHub Desktop.
Type Safe Metadata
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);
}
}
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);
}
}
/**
* 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) { }
}
/**
* 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);
}
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);
}
}
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