Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[B+C] Add ItemMeta.hasCustomData/getCustomData. Adds BUKKIT-3221 #1064

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
37 changes: 37 additions & 0 deletions src/main/java/org/bukkit/inventory/ItemStack.java
Expand Up @@ -12,6 +12,7 @@
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.MaterialData;
import org.bukkit.plugin.Plugin;

/**
* Represents a stack of items
Expand Down Expand Up @@ -600,4 +601,40 @@ private boolean setItemMeta0(ItemMeta itemMeta, Material material) {

return true;
}

/**
* This can be overridden to provide an efficient quick check to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually support subclassing this class so mentioning you can override it to make it faster doesn't make sense. Same this for #hasMetadata(String, Plugin). I'm not sure what you mean about checking for it without unpacking it, can you explain this better? If it means what I think it does that shouldn't be here either since, again, the only supported implementation is the one that you've written here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have worded this poorly- by "subclass" I just meant CraftItemStack, though that really doesn't belong in the comments here.

Basically, the way it's implemented here isn't really useful- but the overridden implementation on CraftItemStack (which I think you will get at least some of, if not the majority of, the time?) provides much quicker access.

One of my main concerns with implementing this as a "get a copy of the data" system is that the data could be quite large, and copying it expensive. I wanted to ensure devs could at least do a quick "is there data here I care about?" check before having to make a copy of all data on the item (even data they don't care about) to inspect it.

So, that said, the implementations of these methods on CraftItemStack do not call the meta methods, but rather call static NBTDatastore methods to do direct inspection of the data without making a copy.

* see if this ItemStack has a specific key in its metadata store,
* without actually unpacking the ItemMeta object.
* <p>
* Use this in place of getItemMeta().hasMetadata("field") for simple first-pass
* checks for data, if you don't necessarily need to unpack the data.
* <p>
* The default implementation defers to ItemMeta.
*
* @param key The String key to check for
* @return True if getItemMeta().hasMetadata(key)
*/
public boolean hasMetadata(String key) {
return meta != null && meta.hasMetadata(key);
}

/**
* This can be overridden to provide an efficient quick check to
* see if this ItemStack has a specific key in its metadata store
* for a specific plugin, without actually unpacking the ItemMeta object.
* <p>
* Use this in place of getItemMeta().hasMetadata("field", plugin) for
* simple first-pass checks for data, if you don't necessarily need to
* unpack the data.
* <p>
* The default implementation defers to ItemMeta.
*
* @param key The String key to check for
* @param plugin The Plugin for which to check for data
* @return True if getItemMeta().hasMetadata(key)
*/
public boolean hasMetadata(String key, Plugin plugin) {
return meta != null && meta.hasMetadata(key, plugin);
}
}
54 changes: 53 additions & 1 deletion src/main/java/org/bukkit/inventory/meta/ItemMeta.java
Expand Up @@ -3,16 +3,23 @@
import java.util.List;
import java.util.Map;

import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.metadata.Metadatable;
import org.bukkit.plugin.Plugin;

/**
* This type represents the storage mechanism for auxiliary item data.
* <p>
* An implementation will handle the creation and application for ItemMeta.
* This class should not be implemented by a plugin in a live environment.
* <p>
* The implementing class should implement Metadatable as a persistent data store attached
* to each ItemStack, and only accept PersistentMetadataValue entries.
*/
public interface ItemMeta extends Cloneable, ConfigurationSerializable {
public interface ItemMeta extends Cloneable, ConfigurationSerializable, Metadatable {

/**
* Checks for existence of a display name.
Expand Down Expand Up @@ -63,6 +70,34 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable {
*/
void setLore(List<String> lore);

/**
* Check to see if the Metadatable store contains any data for any
* Plugin.
*
* @return true if there is any custom data present
*/
public boolean hasMetadata();

/**
* Check to see if the Metadatable store contains a specific key
* for a specific Plugin.
*
* @param key the String key to check for
* @param plugin the Plugin for which to check for data
* @return true if the specified key is registered in this store
* for the specified Plugin
*/
public boolean hasMetadata(String key, Plugin plugin);

/**
* Return a single metadata value for a given key and Plugin.
*
* @param metadataKey the unique metadata key being sought
* @param owningPlugin the plugin that owns the data being sought
* @return the requested value, or null if not found
*/
public MetadataValue getMetadata(String metadataKey, Plugin owningPlugin);

/**
* Checks for the existence of any enchantments.
*
Expand Down Expand Up @@ -124,6 +159,23 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable {
*/
boolean hasConflictingEnchant(Enchantment ench);

/**
* See if this item is glowing for any reason.
* This could be due to enchantments, or to having its gow effect set with setGlowEffect(true)
*
* @return true if this item is glowing
*/
boolean hasGlowEffect();

/**
* Force this item to glow, or remove an item's glow.
*
* Glow can only be removed this way if there are no enchantments on the item.
*
* @param glow if true, a visual glow effect will be added to the item, effect removed if false
*/
void setGlowEffect(boolean glow);

@SuppressWarnings("javadoc")
ItemMeta clone();
}
63 changes: 63 additions & 0 deletions src/main/java/org/bukkit/metadata/PersistentMetadataValue.java
@@ -0,0 +1,63 @@
package org.bukkit.metadata;

import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.plugin.Plugin;

import java.util.List;
import java.util.Map;

/**
* A PersistentMetadataValue is a special case metadata item that may
* only contain ConfigurationSerializable Objects, Lists, Maps or
* basic Object types.
* <p>
* This class is primarily here to serve as a reminder that
* a persistent data store must hold serializable data.
*/
public class PersistentMetadataValue extends MetadataValueAdapter implements MetadataValue {
/**
* Store the internal value that is represented by this value.
* This is expected to be one of a set of a valid serializable types.
*/
private final Object internalValue;

/**
* Initializes a PersistentMetadataValue with an Object value.
* <p>
* The Object must be one of the valid persistable object types.
* Otherwise, an IllegalArgumentException will be thrown.
*
* @throws java.lang.IllegalArgumentException on non-persistable input
* @param owningPlugin the {@link org.bukkit.plugin.Plugin} that created this metadata value
* @param value the value assigned to this metadata value
*/
public PersistentMetadataValue(Plugin owningPlugin, final Object value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, type erasure really sucks, this isn't safe. Map<Player, Location> would pass your check and then fail to persistent later. Don't know if we can fix this but it would be nice to look in to.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a similar issue even without type erasure, unless we actually walk through Lists and Maps here. Otherwise there could always be something non-serializeable buried deep inside.

I will admit the PersistentMetadataValue class feels a little tacked-on right now. I recently re-worked it, originally I had separate constructors for each type it could accept, which actually works a lot better for compile-time validation, and for sort of nudging callers in the right direction.

But I found myself, internally in the NBTMetadataStore, wanting to just be able to call an Object constructor for convenience.

I could put it back the other way, and put the onus of calling the correct constructed based on the the type of data being stored on the caller (including NBTMetadataStore itself). I kind of liked the forced-type constructors, otherwise there's not much point to this class, honestly.

The Map and List constructors could also do recursive type inspection of contents, but I think that will have the type erasure issue one way or another for any nested Lists or Maps.. right?

Anyway, whatever you think is best- this side of the API is kind of loose to me, I really want it to be better but I'm struggling to find a good fit. Another good point someone brought up earlier, it'd be nice if PersistentMetadataValue could somehow throw an error if added to a non-peristent store, though I don't see a way to make that happen.

It was suggested that developers might suddenly think all Metadatable stores supported persistence if they just use this special value type, and since that is not true I can see how this may be confusing in general.

super(owningPlugin);
this.internalValue = value;

if (!(
value instanceof ConfigurationSerializable
|| value instanceof String
|| value instanceof Integer
|| value instanceof Double
|| value instanceof Long
|| value instanceof Boolean
|| value instanceof Short
|| value instanceof Float
|| value instanceof Map
|| value instanceof List
)) {
throw new IllegalArgumentException("Invalid Object type for PersistentMetadataValue: " + value.getClass());
}
}

@Override
public Object value() {
return internalValue;
}

@Override
public void invalidate() {

}
}