diff options
Diffstat (limited to 'src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java')
-rw-r--r-- | src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java new file mode 100644 index 00000000..87709d73 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -0,0 +1,510 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import net.minecraft.server.NBTBase; +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagList; +import net.minecraft.server.NBTTagString; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.craftbukkit.Overridden; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.Repairable; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Children must include the following: + * + * <li> Constructor(CraftMetaItem meta) + * <li> Constructor(NBTTagCompound tag) + * <li> Constructor(Map<String, Object> map) + * <br><br> + * <li> void applyToItem(NBTTagCompound tag) + * <li> boolean applicableTo(Material type) + * <br><br> + * <li> boolean notUncommon(CraftMetaItem meta) + * <li> boolean equalsCommon(CraftMetaItem meta) + * <br><br> + * <li> boolean isEmpty() + * <li> boolean is{Type}Empty() + * <br><br> + * <li> int applyHash() + * <li> public Class clone() + * <br><br> + * <li> Builder<String, Object> serialize(Builder<String, Object> builder) + * <li> SerializableMeta.Deserializers deserializer() + */ +@DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +class CraftMetaItem implements ItemMeta, Repairable { + static class ItemMetaKey { + final String BUKKIT; + final String NBT; + + ItemMetaKey(final String both) { + this(both, both); + } + + ItemMetaKey(final String nbt, final String bukkit) { + this.NBT = nbt; + this.BUKKIT = bukkit; + } + } + + @SerializableAs("ItemMeta") + public static class SerializableMeta implements ConfigurationSerializable { + static final String TYPE_FIELD = "meta-type"; + + enum Deserializers { + BOOK { + @Override + CraftMetaBook deserialize(Map<String, Object> map) { + return new CraftMetaBook(map); + } + }, + SKULL { + @Override + CraftMetaSkull deserialize(Map<String, Object> map) { + return new CraftMetaSkull(map); + } + }, + LEATHER_ARMOR { + @Override + CraftMetaLeatherArmor deserialize(Map<String, Object> map) { + return new CraftMetaLeatherArmor(map); + } + }, + MAP { + @Override + CraftMetaMap deserialize(Map<String, Object> map) { + return new CraftMetaMap(map); + } + }, + POTION { + @Override + CraftMetaPotion deserialize(Map<String, Object> map) { + return new CraftMetaPotion(map); + } + }, + UNSPECIFIC { + @Override + CraftMetaItem deserialize(Map<String, Object> map) { + return new CraftMetaItem(map); + } + }; + + abstract CraftMetaItem deserialize(Map<String, Object> map); + } + + private SerializableMeta() { + } + + public static ItemMeta deserialize(Map<String, Object> map) { + Validate.notNull(map, "Cannot deserialize null map"); + + String type = getString(map, TYPE_FIELD, false); + Deserializers deserializer = Deserializers.valueOf(type); + + if (deserializer == null) { + throw new IllegalArgumentException(type + " is not a valid " + TYPE_FIELD); + } + + return deserializer.deserialize(map); + } + + public Map<String, Object> serialize() { + throw new AssertionError(); + } + + static String getString(Map<?, ?> map, Object field, boolean nullable) { + return getObject(String.class, map, field, nullable); + } + + static boolean getBoolean(Map<?, ?> map, Object field) { + Boolean value = getObject(Boolean.class, map, field, true); + return value != null && value; + } + + static <T> T getObject(Class<T> clazz, Map<?, ?> map, Object field, boolean nullable) { + final Object object = map.get(field); + + if (clazz.isInstance(object)) { + return clazz.cast(object); + } + if (object == null) { + if (!nullable) { + throw new NoSuchElementException(map + " does not contain " + field); + } + return null; + } + throw new IllegalArgumentException(field + "(" + object + ") is not a valid " + clazz); + } + } + + static final ItemMetaKey NAME = new ItemMetaKey("Name", "display-name"); + static final ItemMetaKey DISPLAY = new ItemMetaKey("display"); + static final ItemMetaKey LORE = new ItemMetaKey("Lore", "lore"); + static final ItemMetaKey ENCHANTMENTS = new ItemMetaKey("ench", "enchants"); + static final ItemMetaKey ENCHANTMENTS_ID = new ItemMetaKey("id"); + static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl"); + static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost"); + + private String displayName; + private List<String> lore; + private Map<Enchantment, Integer> enchantments; + private int repairCost; + + CraftMetaItem(CraftMetaItem meta) { + if (meta == null) { + return; + } + + this.displayName = meta.displayName; + + if (meta.hasLore()) { + this.lore = new ArrayList<String>(meta.lore); + } + + if (meta.hasEnchants()) { + this.enchantments = new HashMap<Enchantment, Integer>(meta.enchantments); + } + + this.repairCost = meta.repairCost; + } + + CraftMetaItem(NBTTagCompound tag) { + if (tag.hasKey(DISPLAY.NBT)) { + NBTTagCompound display = tag.getCompound(DISPLAY.NBT); + + if (display.hasKey(NAME.NBT)) { + displayName = display.getString(NAME.NBT); + } + + if (display.hasKey(LORE.NBT)) { + NBTTagList list = display.getList(LORE.NBT); + lore = new ArrayList<String>(list.size()); + + for (int index = 0; index < list.size(); index++) { + String line = ((NBTTagString) list.get(index)).data; + lore.add(line); + } + } + } + + if (tag.hasKey(ENCHANTMENTS.NBT)) { + NBTTagList ench = tag.getList(ENCHANTMENTS.NBT); + enchantments = new HashMap<Enchantment, Integer>(ench.size()); + + for (int i = 0; i < ench.size(); i++) { + short id = ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_ID.NBT); + short level = ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_LVL.NBT); + + addEnchant(Enchantment.getById(id), (int) level, true); + } + } + + if (tag.hasKey(REPAIR.NBT)) { + repairCost = tag.getInt(REPAIR.NBT); + } + } + + CraftMetaItem(Map<String, Object> map) { + setDisplayName(SerializableMeta.getString(map, NAME.BUKKIT, true)); + + if (map.containsKey(LORE.BUKKIT)) { + lore = (List<String>) map.get(LORE.BUKKIT); + } + + Map<?, ?> ench = SerializableMeta.getObject(Map.class, map, ENCHANTMENTS.BUKKIT, true); + if (ench != null) { + enchantments = new HashMap<Enchantment, Integer>(ench.size()); + for (Map.Entry<?, ?> entry : ench.entrySet()) { + Enchantment enchantment = Enchantment.getByName(entry.getKey().toString()); + + if ((enchantment != null) && (entry.getValue() instanceof Integer)) { + addEnchant(enchantment, (Integer) entry.getValue(), true); + } + } + } + + if (map.containsKey(REPAIR.BUKKIT)) { + repairCost = (Integer) map.get(REPAIR.BUKKIT); + } + } + + @Overridden + void applyToItem(NBTTagCompound itemTag) { + if (hasDisplayName()) { + setDisplayTag(itemTag, NAME.NBT, new NBTTagString(NAME.NBT, displayName)); + } + + if (hasLore()) { + NBTTagList list = new NBTTagList(LORE.NBT); + for (int i = 0; i < lore.size(); i++) { + list.add(new NBTTagString(String.valueOf(i), lore.get(i))); + } + setDisplayTag(itemTag, LORE.NBT, list); + } + + if (hasEnchants()) { + NBTTagList list = new NBTTagList(ENCHANTMENTS.NBT); + + for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) { + NBTTagCompound subtag = new NBTTagCompound(); + + subtag.setShort(ENCHANTMENTS_ID.NBT, (short) entry.getKey().getId()); + subtag.setShort(ENCHANTMENTS_LVL.NBT, (short) (int) entry.getValue()); + + list.add(subtag); + } + + itemTag.set(ENCHANTMENTS.NBT, list); + } + + if (hasRepairCost()) { + itemTag.setInt(REPAIR.NBT, repairCost); + } + } + + void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) { + final NBTTagCompound display = tag.getCompound(DISPLAY.NBT); + + if (!tag.hasKey(DISPLAY.NBT)) { + tag.setCompound(DISPLAY.NBT, display); + } + + display.set(key, value); + } + + @Overridden + boolean applicableTo(Material type) { + return type != Material.AIR; + } + + @Overridden + boolean isEmpty() { + return !(hasDisplayName() || hasEnchants() || hasLore()); + } + + public String getDisplayName() { + return displayName; + } + + public final void setDisplayName(String name) { + this.displayName = name; + } + + public boolean hasDisplayName() { + return !Strings.isNullOrEmpty(displayName); + } + + public boolean hasLore() { + return this.lore != null && !this.lore.isEmpty(); + } + + public boolean hasRepairCost() { + return repairCost > 0; + } + + public boolean hasEnchant(Enchantment ench) { + return hasEnchants() ? enchantments.containsKey(ench) : false; + } + + public int getEnchantLevel(Enchantment ench) { + Integer level = hasEnchants() ? enchantments.get(ench) : null; + if (level == null) { + return 0; + } + return level; + } + + public Map<Enchantment, Integer> getEnchants() { + return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.<Enchantment, Integer>of(); + } + + public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + if (enchantments == null) { + enchantments = new HashMap<Enchantment, Integer>(4); + } + + if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { + Integer old = enchantments.put(ench, level); + return old == null || old != level; + } + return false; + } + + public boolean removeEnchant(Enchantment ench) { + return hasEnchants() ? enchantments.remove(ench) != null : false; + } + + public boolean hasEnchants() { + return !(enchantments == null || enchantments.isEmpty()); + } + + public List<String> getLore() { + return this.lore == null ? null : new ArrayList<String>(this.lore); + } + + public void setLore(List<String> lore) { // too tired to think if .clone is better + if (lore == null) { + this.lore = null; + } else { + if (this.lore == null) { + safelyAdd(lore, this.lore = new ArrayList<String>(lore.size()), Integer.MAX_VALUE); + } else { + this.lore.clear(); + safelyAdd(lore, this.lore, Integer.MAX_VALUE); + } + } + } + + public int getRepairCost() { + return repairCost; + } + + public void setRepairCost(int cost) { // TODO: Does this have limits? + repairCost = cost; + } + + @Override + public final boolean equals(Object object) { + if (object == null) { + return false; + } + if (object == this) { + return true; + } + if (!(object instanceof CraftMetaItem)) { + return false; + } + return CraftItemFactory.instance().equals(this, (ItemMeta) object); + } + + /** + * This method is almost as weird as notUncommon. + * Only return false if your common internals are unequal. + * Checking your own internals is redundant if you are not common, as notUncommon is meant for checking those 'not common' variables. + */ + @Overridden + boolean equalsCommon(CraftMetaItem that) { + return ((this.hasDisplayName() ? that.hasDisplayName() && this.displayName.equals(that.displayName) : !that.hasDisplayName())) + && (this.hasEnchants() ? that.hasEnchants() && this.enchantments.equals(that.enchantments) : !that.hasEnchants()) + && (this.hasLore() ? that.hasLore() && this.lore.equals(that.lore) : !that.hasLore()) + && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()); + } + + /** + * This method is a bit weird... + * Return true if you are a common class OR your uncommon parts are empty. + * Empty uncommon parts implies the NBT data would be equivalent if both were applied to an item + */ + @Overridden + boolean notUncommon(CraftMetaItem meta) { + return true; + } + + @Override + public final int hashCode() { + return applyHash(); + } + + @Overridden + int applyHash() { + int hash = 3; + hash = 61 * hash + (hasDisplayName() ? this.displayName.hashCode() : 0); + hash = 61 * hash + (hasLore() ? this.lore.hashCode() : 0); + hash = 61 * hash + (hasEnchants() ? this.enchantments.hashCode() : 0); + hash = 61 * hash + (hasRepairCost() ? this.repairCost : 0); + return hash; + } + + @Overridden + @Override + public CraftMetaItem clone() { + try { + return (CraftMetaItem) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + public final Map<String, Object> serialize() { + ImmutableMap.Builder<String, Object> map = ImmutableMap.builder(); + map.put(SerializableMeta.TYPE_FIELD, deserializer().name()); + serialize(map); + return map.build(); + } + + @Overridden + ImmutableMap.Builder<String, Object> serialize(ImmutableMap.Builder<String, Object> builder) { + if (hasDisplayName()) { + builder.put(NAME.BUKKIT, displayName); + } + + if (hasLore()) { + builder.put(LORE.BUKKIT, ImmutableList.copyOf(lore)); + } + + if (hasEnchants()) { + ImmutableMap.Builder<String, Integer> enchantments = ImmutableMap.builder(); + for (Map.Entry<Enchantment, Integer> enchant : this.enchantments.entrySet()) { + enchantments.put(enchant.getKey().getName(), enchant.getValue()); + } + builder.put(ENCHANTMENTS.BUKKIT, enchantments.build()); + } + + if (hasRepairCost()) { + builder.put(REPAIR.BUKKIT, repairCost); + } + + return builder; + } + + @Overridden + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.UNSPECIFIC; + } + + static void safelyAdd(Collection<?> addFrom, Collection<String> addTo, int maxItemLength) { + if (addFrom == null) { + return; + } + + for (Object object : addFrom) { + if (!(object instanceof String)) { + if (object != null) { + throw new IllegalArgumentException(addFrom + " cannot contain non-string " + object.getClass().getName()); + } + + addTo.add(""); + } else { + String page = object.toString(); + + if (page.length() > maxItemLength) { + page = page.substring(0, maxItemLength); + } + + addTo.add(page); + } + } + } + + @Override + public final String toString() { + return serialize().toString(); // TODO: cry + } +} |