Add ItemMeta factory and interfaces. This adds BUKKIT-15
Included with ItemMeta is a new serializable class Color. PotionEffects are now serializable.
This commit is contained in:
@@ -1,48 +1,97 @@
|
||||
package org.bukkit.inventory;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Utility;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.material.MaterialData;
|
||||
|
||||
/**
|
||||
* Represents a stack of items
|
||||
*/
|
||||
public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
private int type;
|
||||
private int type = 0;
|
||||
private int amount = 0;
|
||||
private MaterialData data = null;
|
||||
private short durability = 0;
|
||||
private Map<Enchantment, Integer> enchantments = new HashMap<Enchantment, Integer>();
|
||||
private ItemMeta meta;
|
||||
|
||||
@Utility
|
||||
protected ItemStack() {}
|
||||
|
||||
/**
|
||||
* Defaults stack size to 1, with no extra data
|
||||
*
|
||||
* @param type item material id
|
||||
*/
|
||||
public ItemStack(final int type) {
|
||||
this(type, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults stack size to 1, with no extra data
|
||||
*
|
||||
* @param type item material
|
||||
*/
|
||||
public ItemStack(final Material type) {
|
||||
this(type, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* An item stack with no extra data
|
||||
*
|
||||
* @param type item material id
|
||||
* @param amount stack size
|
||||
*/
|
||||
public ItemStack(final int type, final int amount) {
|
||||
this(type, amount, (short) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* An item stack with no extra data
|
||||
*
|
||||
* @param type item material
|
||||
* @param amount stack size
|
||||
*/
|
||||
public ItemStack(final Material type, final int amount) {
|
||||
this(type.getId(), amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* An item stack with the specified damage / durability
|
||||
*
|
||||
* @param type item material id
|
||||
* @param amount stack size
|
||||
* @param damage durability / damage
|
||||
*/
|
||||
public ItemStack(final int type, final int amount, final short damage) {
|
||||
this(type, amount, damage, null);
|
||||
this.type = type;
|
||||
this.amount = amount;
|
||||
this.durability = damage;
|
||||
}
|
||||
|
||||
/**
|
||||
* An item stack with the specified damage / durabiltiy
|
||||
*
|
||||
* @param type item material
|
||||
* @param amount stack size
|
||||
* @param damage durability / damage
|
||||
*/
|
||||
public ItemStack(final Material type, final int amount, final short damage) {
|
||||
this(type.getId(), amount, damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this method uses an ambiguous data byte object
|
||||
*/
|
||||
@Deprecated
|
||||
public ItemStack(final int type, final int amount, final short damage, final Byte data) {
|
||||
this.type = type;
|
||||
this.amount = amount;
|
||||
@@ -53,18 +102,29 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this method uses an ambiguous data byte object
|
||||
*/
|
||||
@Deprecated
|
||||
public ItemStack(final Material type, final int amount, final short damage, final Byte data) {
|
||||
this(type.getId(), amount, damage, data);
|
||||
}
|
||||
|
||||
public ItemStack(final ItemStack stack) {
|
||||
this.type = stack.type;
|
||||
this.amount = stack.amount;
|
||||
this.durability = stack.durability;
|
||||
if (stack.data != null) {
|
||||
this.data = stack.data.clone();
|
||||
/**
|
||||
* Creates a new item stack derived from the specified stack
|
||||
*
|
||||
* @param stack the stack to copy
|
||||
* @throws IllegalArgumentException if the specified stack is null or returns an item meta not created by the item factory
|
||||
*/
|
||||
public ItemStack(final ItemStack stack) throws IllegalArgumentException {
|
||||
Validate.notNull(stack, "Cannot copy null stack");
|
||||
this.type = stack.getTypeId();
|
||||
this.amount = stack.getAmount();
|
||||
this.durability = stack.getDurability();
|
||||
this.data = stack.getData();
|
||||
if (stack.hasItemMeta()) {
|
||||
setItemMeta0(stack.getItemMeta(), getType0());
|
||||
}
|
||||
this.addUnsafeEnchantments(stack.getEnchantments());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,8 +132,18 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
*
|
||||
* @return Type of the items in this stack
|
||||
*/
|
||||
@Utility
|
||||
public Material getType() {
|
||||
return Material.getMaterial(type);
|
||||
return getType0(getTypeId());
|
||||
}
|
||||
|
||||
private Material getType0() {
|
||||
return getType0(this.type);
|
||||
}
|
||||
|
||||
private static Material getType0(int id) {
|
||||
Material material = Material.getMaterial(id);
|
||||
return material == null ? Material.AIR : material;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +153,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
*
|
||||
* @param type New type to set the items in this stack to
|
||||
*/
|
||||
@Utility
|
||||
public void setType(Material type) {
|
||||
Validate.notNull(type, "Material cannot be null");
|
||||
setTypeId(type.getId());
|
||||
}
|
||||
|
||||
@@ -105,6 +177,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
*/
|
||||
public void setTypeId(int type) {
|
||||
this.type = type;
|
||||
if (this.meta != null) {
|
||||
this.meta = Bukkit.getItemFactory().asMetaFor(meta, getType0());
|
||||
}
|
||||
createData((byte) 0);
|
||||
}
|
||||
|
||||
@@ -132,9 +207,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* @return MaterialData for this item
|
||||
*/
|
||||
public MaterialData getData() {
|
||||
Material mat = Material.getMaterial(getTypeId());
|
||||
if (mat != null && mat.getData() != null) {
|
||||
data = mat.getNewData((byte) this.durability);
|
||||
Material mat = getType();
|
||||
if (data == null && mat != null && mat.getData() != null) {
|
||||
data = mat.getNewData((byte) this.getDurability());
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -148,7 +223,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
public void setData(MaterialData data) {
|
||||
Material mat = getType();
|
||||
|
||||
if ((mat == null) || (mat.getData() == null)) {
|
||||
if (data == null || mat == null || mat.getData() == null) {
|
||||
this.data = data;
|
||||
} else {
|
||||
if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) {
|
||||
@@ -183,12 +258,12 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
*
|
||||
* @return The maximum you can stack this material to.
|
||||
*/
|
||||
@Utility
|
||||
public int getMaxStackSize() {
|
||||
Material material = getType();
|
||||
if (material != null) {
|
||||
return material.getMaxStackSize();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -203,19 +278,44 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Utility
|
||||
public String toString() {
|
||||
return "ItemStack{" + getType().name() + " x " + getAmount() + "}";
|
||||
StringBuilder toString = new StringBuilder("ItemStack{").append(getType().name()).append(" x ").append(getAmount());
|
||||
if (hasItemMeta()) {
|
||||
toString.append(", ").append(getItemMeta());
|
||||
}
|
||||
return toString.append('}').toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Utility
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ItemStack)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemStack item = (ItemStack) obj;
|
||||
ItemStack stack = (ItemStack) obj;
|
||||
return getAmount() == stack.getAmount() && isSimilar(stack);
|
||||
}
|
||||
|
||||
return item.getAmount() == getAmount() && item.getTypeId() == getTypeId() && getDurability() == item.getDurability() && getEnchantments().equals(item.getEnchantments());
|
||||
/**
|
||||
* This method is the same as equals, but does not consider stack size (amount).
|
||||
*
|
||||
* @param stack the item stack to compare to
|
||||
* @return true if the two stacks are equal, ignoring the amount
|
||||
*/
|
||||
@Utility
|
||||
public boolean isSimilar(ItemStack stack) {
|
||||
if (stack == null) {
|
||||
return false;
|
||||
}
|
||||
if (stack == this) {
|
||||
return true;
|
||||
}
|
||||
return getTypeId() == stack.getTypeId() && getDurability() == stack.getDurability() && hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,7 +323,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
try {
|
||||
ItemStack itemStack = (ItemStack) super.clone();
|
||||
|
||||
itemStack.enchantments = new HashMap<Enchantment, Integer>(this.enchantments);
|
||||
if (this.meta != null) {
|
||||
itemStack.meta = this.meta.clone();
|
||||
}
|
||||
|
||||
if (this.data != null) {
|
||||
itemStack.data = this.data.clone();
|
||||
}
|
||||
@@ -235,11 +338,15 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 11;
|
||||
@Utility
|
||||
public final int hashCode() {
|
||||
int hash = 1;
|
||||
|
||||
hash = hash * 31 + getTypeId();
|
||||
hash = hash * 31 + getAmount();
|
||||
hash = hash * 31 + (getDurability() & 0xffff);
|
||||
hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0);
|
||||
|
||||
hash = hash * 19 + 7 * getTypeId(); // Overriding hashCode since equals is overridden, it's just
|
||||
hash = hash * 7 + 23 * getAmount(); // too bad these are mutable values... Q_Q
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -250,7 +357,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* @return True if this has the given enchantment
|
||||
*/
|
||||
public boolean containsEnchantment(Enchantment ench) {
|
||||
return enchantments.containsKey(ench);
|
||||
return meta == null ? false : meta.hasEnchant(ench);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +367,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* @return Level of the enchantment, or 0
|
||||
*/
|
||||
public int getEnchantmentLevel(Enchantment ench) {
|
||||
return enchantments.get(ench);
|
||||
return meta == null ? 0 : meta.getEnchantLevel(ench);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +376,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* @return Map of enchantments.
|
||||
*/
|
||||
public Map<Enchantment, Integer> getEnchantments() {
|
||||
return ImmutableMap.copyOf(enchantments);
|
||||
return meta == null ? ImmutableMap.<Enchantment, Integer>of() : meta.getEnchants();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,8 +386,13 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* for each element of the map.
|
||||
*
|
||||
* @param enchantments Enchantments to add
|
||||
* @throws IllegalArgumentException if the specified enchantments is null
|
||||
* @throws IllegalArgumentException if any specific enchantment or level is null.
|
||||
* <b>Warning</b>: Some enchantments may be added before this exception is thrown.
|
||||
*/
|
||||
@Utility
|
||||
public void addEnchantments(Map<Enchantment, Integer> enchantments) {
|
||||
Validate.notNull(enchantments, "Enchantments cannot be null");
|
||||
for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
|
||||
addEnchantment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
@@ -293,8 +405,11 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
*
|
||||
* @param ench Enchantment to add
|
||||
* @param level Level of the enchantment
|
||||
* @throws IllegalArgumentException if enchantment null, or enchantment is not applicable
|
||||
*/
|
||||
@Utility
|
||||
public void addEnchantment(Enchantment ench, int level) {
|
||||
Validate.notNull(ench, "Enchantment cannot be null");
|
||||
if ((level < ench.getStartLevel()) || (level > ench.getMaxLevel())) {
|
||||
throw new IllegalArgumentException("Enchantment level is either too low or too high (given " + level + ", bounds are " + ench.getStartLevel() + " to " + ench.getMaxLevel());
|
||||
} else if (!ench.canEnchantItem(this)) {
|
||||
@@ -312,6 +427,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
*
|
||||
* @param enchantments Enchantments to add
|
||||
*/
|
||||
@Utility
|
||||
public void addUnsafeEnchantments(Map<Enchantment, Integer> enchantments) {
|
||||
for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
|
||||
addUnsafeEnchantment(entry.getKey(), entry.getValue());
|
||||
@@ -330,7 +446,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* @param level Level of the enchantment
|
||||
*/
|
||||
public void addUnsafeEnchantment(Enchantment ench, int level) {
|
||||
enchantments.put(ench, level);
|
||||
(meta == null ? meta = Bukkit.getItemFactory().getItemMeta(getType0()) : meta).addEnchant(ench, level, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,38 +456,43 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
* @return Previous level, or 0
|
||||
*/
|
||||
public int removeEnchantment(Enchantment ench) {
|
||||
Integer previous = enchantments.remove(ench);
|
||||
return (previous == null) ? 0 : previous;
|
||||
int level = getEnchantmentLevel(ench);
|
||||
if (level == 0 || meta == null) {
|
||||
return level;
|
||||
}
|
||||
meta.removeEnchant(ench);
|
||||
return level;
|
||||
}
|
||||
|
||||
@Utility
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||
|
||||
result.put("type", getType().name());
|
||||
|
||||
if (durability != 0) {
|
||||
result.put("damage", durability);
|
||||
if (getDurability() != 0) {
|
||||
result.put("damage", getDurability());
|
||||
}
|
||||
|
||||
if (amount != 1) {
|
||||
result.put("amount", amount);
|
||||
if (getAmount() != 1) {
|
||||
result.put("amount", getAmount());
|
||||
}
|
||||
|
||||
Map<Enchantment, Integer> enchants = getEnchantments();
|
||||
|
||||
if (enchants.size() > 0) {
|
||||
Map<String, Integer> safeEnchants = new HashMap<String, Integer>();
|
||||
|
||||
for (Map.Entry<Enchantment, Integer> entry : enchants.entrySet()) {
|
||||
safeEnchants.put(entry.getKey().getName(), entry.getValue());
|
||||
}
|
||||
|
||||
result.put("enchantments", safeEnchants);
|
||||
ItemMeta meta = getItemMeta();
|
||||
if (!Bukkit.getItemFactory().equals(meta, null)) {
|
||||
result.put("meta", meta);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required method for configuration serialization
|
||||
*
|
||||
* @param args map to deserialize
|
||||
* @return deserialized item stack
|
||||
* @see ConfigurationSerializable
|
||||
*/
|
||||
public static ItemStack deserialize(Map<String, Object> args) {
|
||||
Material type = Material.getMaterial((String) args.get("type"));
|
||||
short damage = 0;
|
||||
@@ -387,7 +508,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
|
||||
ItemStack result = new ItemStack(type, amount, damage);
|
||||
|
||||
if (args.containsKey("enchantments")) {
|
||||
if (args.containsKey("enchantments")) { // Backward compatiblity, @deprecated
|
||||
Object raw = args.get("enchantments");
|
||||
|
||||
if (raw instanceof Map) {
|
||||
@@ -401,8 +522,61 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (args.containsKey("meta")) { // We cannot and will not have meta when enchantments (pre-ItemMeta) exist
|
||||
Object raw = args.get("meta");
|
||||
if (raw instanceof ItemMeta) {
|
||||
result.setItemMeta((ItemMeta) raw);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a copy of this ItemStack's {@link ItemMeta}.
|
||||
*
|
||||
* @return a copy of the current ItemStack's ItemData
|
||||
*/
|
||||
public ItemMeta getItemMeta() {
|
||||
return this.meta == null ? Bukkit.getItemFactory().getItemMeta(getType0()) : this.meta.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if any meta data has been defined.
|
||||
*
|
||||
* @return Returns true if some meta data has been set for this item
|
||||
*/
|
||||
public boolean hasItemMeta() {
|
||||
return !Bukkit.getItemFactory().equals(meta, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ItemMeta of this ItemStack.
|
||||
*
|
||||
* @param itemMeta new ItemMeta, or null to indicate meta data be cleared.
|
||||
* @return True if successfully applied ItemMeta, see {@link ItemFactory#isApplicable(ItemMeta, ItemStack)}
|
||||
* @throws IllegalArgumentException if the item meta was not created by the {@link ItemFactory}
|
||||
*/
|
||||
public boolean setItemMeta(ItemMeta itemMeta) {
|
||||
return setItemMeta0(itemMeta, getType0());
|
||||
}
|
||||
|
||||
/*
|
||||
* Cannot be overridden, so it's safe for constructor call
|
||||
*/
|
||||
private boolean setItemMeta0(ItemMeta itemMeta, Material material) {
|
||||
if (itemMeta == null) {
|
||||
this.meta = null;
|
||||
return true;
|
||||
}
|
||||
if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) {
|
||||
return false;
|
||||
}
|
||||
this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material);
|
||||
if (this.meta == itemMeta) {
|
||||
this.meta = itemMeta.clone();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user