Newer
Older
ultramine_bukkit / src / main / java / org / bukkit / inventory / ItemStack.java
@vlad20012 vlad20012 on 24 Feb 2017 15 KB initial
package org.bukkit.inventory;

import com.google.common.collect.ImmutableMap;
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;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Represents a stack of items
 */
public class ItemStack implements Cloneable, ConfigurationSerializable
{
	private int type = 0;
	private int amount = 0;
	private MaterialData data = null;
	private short durability = 0;
	private ItemMeta meta;

	@Utility
	protected ItemStack()
	{
	}

	/**
	 * Defaults stack size to 1, with no extra data
	 *
	 * @param type item material id
	 * @deprecated Magic value
	 */
	@Deprecated
	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
	 * @deprecated Magic value
	 */
	@Deprecated
	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
	 * @deprecated Magic value
	 */
	@Deprecated
	public ItemStack(final int type, final int amount, final short damage)
	{
		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;
		this.durability = damage;
		if(data != null)
		{
			createData(data);
			this.durability = data;
		}
	}

	/**
	 * @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);
	}

	/**
	 * 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());
		}
	}

	/**
	 * Gets the type of this item
	 *
	 * @return Type of the items in this stack
	 */
	@Utility
	public Material getType()
	{
		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;
	}

	/**
	 * Sets the type of this item
	 * <p>
	 * Note that in doing so you will reset the MaterialData for this stack
	 *
	 * @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());
	}

	/**
	 * Gets the type id of this item
	 *
	 * @return Type Id of the items in this stack
	 * @deprecated Magic value
	 */
	@Deprecated
	public int getTypeId()
	{
		return type;
	}

	/**
	 * Sets the type id of this item
	 * <p>
	 * Note that in doing so you will reset the MaterialData for this stack
	 *
	 * @param type New type id to set the items in this stack to
	 * @deprecated Magic value
	 */
	@Deprecated
	public void setTypeId(int type)
	{
		this.type = type;
		if(this.meta != null)
		{
			this.meta = Bukkit.getItemFactory().asMetaFor(meta, getType0());
		}
		createData((byte) 0);
	}

	/**
	 * Gets the amount of items in this stack
	 *
	 * @return Amount of items in this stick
	 */
	public int getAmount()
	{
		return amount;
	}

	/**
	 * Sets the amount of items in this stack
	 *
	 * @param amount New amount of items in this stack
	 */
	public void setAmount(int amount)
	{
		this.amount = amount;
	}

	/**
	 * Gets the MaterialData for this stack of items
	 *
	 * @return MaterialData for this item
	 */
	public MaterialData getData()
	{
		Material mat = getType();
		if(data == null && mat != null && mat.getData() != null)
		{
			data = mat.getNewData((byte) this.getDurability());
		}

		return data;
	}

	/**
	 * Sets the MaterialData for this stack of items
	 *
	 * @param data New MaterialData for this item
	 */
	public void setData(MaterialData data)
	{
		Material mat = getType();

		if(data == null || mat == null || mat.getData() == null)
		{
			this.data = data;
		}
		else
		{
			if((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class))
			{
				this.data = data;
			}
			else
			{
				throw new IllegalArgumentException("Provided data is not of type " + mat.getData().getName() + ", found " + data.getClass().getName());
			}
		}
	}

	/**
	 * Sets the durability of this item
	 *
	 * @param durability Durability of this item
	 */
	public void setDurability(final short durability)
	{
		this.durability = durability;
	}

	/**
	 * Gets the durability of this item
	 *
	 * @return Durability of this item
	 */
	public short getDurability()
	{
		return durability;
	}

	/**
	 * Get the maximum stacksize for the material hold in this ItemStack.
	 * (Returns -1 if it has no idea)
	 *
	 * @return The maximum you can stack this material to.
	 */
	@Utility
	public int getMaxStackSize()
	{
		Material material = getType();
		if(material != null)
		{
			return material.getMaxStackSize();
		}
		return -1;
	}

	private void createData(final byte data)
	{
		Material mat = Material.getMaterial(type);

		if(mat == null)
		{
			this.data = new MaterialData(type, data);
		}
		else
		{
			this.data = mat.getNewData(data);
		}
	}

	@Override
	@Utility
	public String toString()
	{
		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 stack = (ItemStack) obj;
		return getAmount() == stack.getAmount() && isSimilar(stack);
	}

	/**
	 * 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
	public ItemStack clone()
	{
		try
		{
			ItemStack itemStack = (ItemStack) super.clone();

			if(this.meta != null)
			{
				itemStack.meta = this.meta.clone();
			}

			if(this.data != null)
			{
				itemStack.data = this.data.clone();
			}

			return itemStack;
		} catch(CloneNotSupportedException e)
		{
			throw new Error(e);
		}
	}

	@Override
	@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);

		return hash;
	}

	/**
	 * Checks if this ItemStack contains the given {@link Enchantment}
	 *
	 * @param ench Enchantment to test
	 * @return True if this has the given enchantment
	 */
	public boolean containsEnchantment(Enchantment ench)
	{
		return meta == null ? false : meta.hasEnchant(ench);
	}

	/**
	 * Gets the level of the specified enchantment on this item stack
	 *
	 * @param ench Enchantment to check
	 * @return Level of the enchantment, or 0
	 */
	public int getEnchantmentLevel(Enchantment ench)
	{
		return meta == null ? 0 : meta.getEnchantLevel(ench);
	}

	/**
	 * Gets a map containing all enchantments and their levels on this item.
	 *
	 * @return Map of enchantments.
	 */
	public Map<Enchantment, Integer> getEnchantments()
	{
		return meta == null ? ImmutableMap.<Enchantment, Integer>of() : meta.getEnchants();
	}

	/**
	 * Adds the specified enchantments to this item stack.
	 * <p>
	 * This method is the same as calling {@link
	 * #addEnchantment(org.bukkit.enchantments.Enchantment, int)} 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());
		}
	}

	/**
	 * Adds the specified {@link Enchantment} to this item stack.
	 * <p>
	 * If this item stack already contained the given enchantment (at any
	 * level), it will be replaced.
	 *
	 * @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))
		{
			throw new IllegalArgumentException("Specified enchantment cannot be applied to this itemstack");
		}

		addUnsafeEnchantment(ench, level);
	}

	/**
	 * Adds the specified enchantments to this item stack in an unsafe manner.
	 * <p>
	 * This method is the same as calling {@link
	 * #addUnsafeEnchantment(org.bukkit.enchantments.Enchantment, int)} for
	 * each element of the map.
	 *
	 * @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());
		}
	}

	/**
	 * Adds the specified {@link Enchantment} to this item stack.
	 * <p>
	 * If this item stack already contained the given enchantment (at any
	 * level), it will be replaced.
	 * <p>
	 * This method is unsafe and will ignore level restrictions or item type.
	 * Use at your own discretion.
	 *
	 * @param ench  Enchantment to add
	 * @param level Level of the enchantment
	 */
	public void addUnsafeEnchantment(Enchantment ench, int level)
	{
		(meta == null ? meta = Bukkit.getItemFactory().getItemMeta(getType0()) : meta).addEnchant(ench, level, true);
	}

	/**
	 * Removes the specified {@link Enchantment} if it exists on this
	 * ItemStack
	 *
	 * @param ench Enchantment to remove
	 * @return Previous level, or 0
	 */
	public int removeEnchantment(Enchantment ench)
	{
		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(getDurability() != 0)
		{
			result.put("damage", getDurability());
		}

		if(getAmount() != 1)
		{
			result.put("amount", getAmount());
		}

		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;
		int amount = 1;

		if(args.containsKey("damage"))
		{
			damage = ((Number) args.get("damage")).shortValue();
		}

		if(args.containsKey("amount"))
		{
			amount = (Integer) args.get("amount");
		}

		ItemStack result = new ItemStack(type, amount, damage);

		if(args.containsKey("enchantments"))
		{ // Backward compatiblity, @deprecated
			Object raw = args.get("enchantments");

			if(raw instanceof Map)
			{
				Map<?, ?> map = (Map<?, ?>) raw;

				for(Map.Entry<?, ?> entry : map.entrySet())
				{
					Enchantment enchantment = Enchantment.getByName(entry.getKey().toString());

					if((enchantment != null) && (entry.getValue() instanceof Integer))
					{
						result.addUnsafeEnchantment(enchantment, (Integer) entry.getValue());
					}
				}
			}
		}
		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;
	}
}