/*
* This file is part of Skript.
*
* Skript is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Skript is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/
package ch.njol.skript.aliases;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.RandomAccess;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.Skript;
import ch.njol.skript.classes.ConfigurationSerializer;
import ch.njol.skript.lang.Unit;
import ch.njol.skript.localization.Adjective;
import ch.njol.skript.localization.GeneralWords;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.Message;
import ch.njol.skript.localization.Noun;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.BlockUtils;
import ch.njol.skript.util.Container;
import ch.njol.skript.util.Container.ContainerType;
import ch.njol.skript.util.EnchantmentType;
import ch.njol.skript.util.Utils;
import ch.njol.util.coll.iterator.EmptyIterable;
import ch.njol.util.coll.iterator.SingleItemIterable;
import ch.njol.yggdrasil.Fields;
import ch.njol.yggdrasil.YggdrasilSerializable.YggdrasilExtendedSerializable;
@ContainerType(ItemStack.class)
@SuppressWarnings("deprecation")
public class ItemType implements Unit, Iterable<ItemData>, Container<ItemStack>, YggdrasilExtendedSerializable {
private final static Message m_named = new Message("aliases.named");
// 1.4.5
public final static boolean itemMetaSupported = Skript.supports("org.bukkit.inventory.meta.ItemMeta");
/**
* Note to self: use {@link #add_(ItemData)} to add item datas, don't add them directly to this list.
*/
final ArrayList<ItemData> types = new ArrayList<ItemData>();
private boolean all = false;
private int amount = -1;
/**
* How many different items this item type represents
*/
private int numItems = 0;
// TODO empty == unenchanted, add expression "unenchanted <item>"
@Nullable
transient Map<Enchantment, Integer> enchantments = null;
/**
* Guaranteed to be of type ItemMeta.
*/
@Nullable
transient Object meta = null;
/**
* ItemTypes to use instead of this one if adding to an inventory or setting a block.
*/
@Nullable
private ItemType item = null, block = null;
void setItem(final @Nullable ItemType item) {
if (equals(item)) { // can happen if someone defines a 'x' and 'x item/block' alias that have the same value, e.g. 'dirt' and 'dirt block'
this.item = null;
} else {
if (item != null) {
if (item.item != null || item.block != null) {
assert false : this + "; item=" + item + ", item.item=" + item.item + ", item.block=" + item.block;
this.item = null;
return;
}
item.setAmount(amount);
}
this.item = item;
}
}
void setBlock(final @Nullable ItemType block) {
if (equals(block)) {
this.block = null;
} else {
if (block != null) {
if (block.item != null || block.block != null) {
assert false : this + "; block=" + block + ", block.item=" + block.item + ", block.block=" + block.block;
this.block = null;
return;
}
block.setAmount(amount);
}
this.block = block;
}
}
public ItemType() {}
public ItemType(final int id) {
add_(new ItemData(id));
}
public ItemType(final int id, final short data) {
add_(new ItemData(id, data));
}
public ItemType(final ItemData d) {
add_(d.clone());
}
public ItemType(final ItemStack i) {
amount = i.getAmount();
add_(new ItemData(i));
if (!i.getEnchantments().isEmpty())
enchantments = new HashMap<Enchantment, Integer>(i.getEnchantments());
if (itemMetaSupported) {
meta = i.getItemMeta();
unsetItemMetaEnchs((ItemMeta) meta);
}
}
public ItemType(final Block b) {
// amount = 1;
add_(new ItemData(b.getTypeId(), b.getData()));
// TODO metadata - spawners, skulls, etc.
}
private ItemType(final ItemType i) {
all = i.all;
amount = i.amount;
numItems = i.numItems;
final ItemType bl = i.block, it = i.item;
block = bl == null ? null : bl.clone();
item = it == null ? null : it.clone();
final Object m = i.meta;
meta = m == null ? null : ((ItemMeta) m).clone();
for (final ItemData d : i) {
types.add(d.clone());
}
if (i.enchantments != null)
enchantments = new HashMap<Enchantment, Integer>(i.enchantments);
}
/**
* Removes the item and block aliases from this alias as it now represents a different item.
*/
public void modified() {
item = block = null;
}
/**
* @return amount or 1 if amount == -1
*/
@Override
public int getAmount() {
return amount == -1 ? 1 : amount;
}
/**
* Only use this method if you know what you're doing.
*
* @return the internal amount, i.e. -1 or the same as {@link #getAmount()}.
*/
public int getInternalAmount() {
return amount;
}
@Override
public void setAmount(final double amount) {
setAmount((int) amount);
}
public void setAmount(final int amount) {
this.amount = amount;
if (item != null)
item.amount = amount;
if (block != null)
block.amount = amount;
}
public boolean isAll() {
return all;
}
public void setAll(final boolean all) {
this.all = all;
}
@Nullable
public Object getItemMeta() {
return meta;
}
public void setItemMeta(final Object meta) {
if (!itemMetaSupported || !(meta instanceof ItemMeta))
throw new IllegalStateException("" + meta);
unsetItemMetaEnchs((ItemMeta) meta);
this.meta = meta;
if (item != null) {
item = item.clone();
item.meta = meta;
}
if (block != null) {
block = block.clone();
block.meta = meta;
}
}
private final static void unsetItemMetaEnchs(final @Nullable ItemMeta meta) {
if (meta == null)
return;
for (final Enchantment e : meta.getEnchants().keySet())
meta.removeEnchant(e);
}
/**
* @param item
* @return Whether the given item has correct enchantments & ItemMeta, but doesn't check its type
*/
private boolean hasMeta(final @Nullable ItemStack item) {
final Map<Enchantment, Integer> enchs = enchantments;
if (enchs != null) {
if (item == null)
return false;
for (final Entry<Enchantment, Integer> e : enchs.entrySet()) {
if (e.getValue() == -1 ? item.getEnchantmentLevel(e.getKey()) == 0 : item.getEnchantmentLevel(e.getKey()) != e.getValue())
return false;
}
}
final Object meta = this.meta;
if (meta != null) {
if (item == null)
return false;
final ItemMeta m = item.getItemMeta();
unsetItemMetaEnchs(m);
if (!meta.equals(m))
return false;
}
return true;
}
public boolean isOfType(final @Nullable ItemStack item) {
if (item == null)
return isOfType(0, (short) 0);
if (!hasMeta(item))
return false;
return isOfType(item.getTypeId(), item.getDurability());
}
public boolean isOfType(final @Nullable Block block) {
if (enchantments != null)
return false;
if (block == null)
return isOfType(0, (short) 0);
return isOfType(block.getTypeId(), block.getData());
// TODO metadata
}
public boolean isOfType(final int id, final short data) {
for (final ItemData type : types) {
if (type.isOfType(id, data))
return true;
}
return false;
}
public boolean isSupertypeOf(final ItemType other) {
// if (all != other.all)
// return false;
if (amount != -1 && other.amount != amount)
return false;
final Map<Enchantment, Integer> enchs = enchantments;
if (enchs != null) {
final Map<Enchantment, Integer> oenchs = other.enchantments;
if (oenchs == null)
return false;
for (final Entry<Enchantment, Integer> o : oenchs.entrySet()) {
final Integer t = enchs.get(o.getKey());
if (t == null || !t.equals(o.getValue()))
return false;
}
}
if (meta != null && !meta.equals(other.meta))
return false;
outer: for (final ItemData o : other.types) {
assert o != null;
for (final ItemData t : types) {
if (t.isSupertypeOf(o))
continue outer;
}
return false;
}
return true;
}
public ItemType getItem() {
final ItemType item = this.item;
return item == null ? this : item;
}
public ItemType getBlock() {
// if (block == null) {
// ItemType i = clone();
// for (int j = 0; j < i.numTypes(); j++) {
// final ItemData d = i.getTypes().get(j);
// if (d.getId() > Skript.MAXBLOCKID) {
// i.remove(d);
// j--;
// }
// }
// if (i.getTypes().isEmpty())
// return this;
// return block = i;
// }
final ItemType block = this.block;
return block == null ? this : block;
}
/**
* @return Whether this ItemType has at least one ItemData that represents an item
*/
public boolean hasItem() {
for (final ItemData d : types) {
if (d.getId() > Skript.MAXBLOCKID)
return true;
}
return false;
}
/**
* @return Whether this ItemType has at least one ItemData that represents a block
*/
public boolean hasBlock() {
for (final ItemData d : types) {
if (d.getId() <= Skript.MAXBLOCKID)
return true;
}
return false;
}
/**
* Sets the given block to this ItemType
*
* @param block The block to set
* @param applyPhysics Whether to run a physics check just after setting the block
* @return Whether the block was successfully set
*/
public boolean setBlock(final Block block, final boolean applyPhysics) {
for (final ItemData d : types) {
if (d.typeid > Skript.MAXBLOCKID)
continue;
if (BlockUtils.set(block, d.typeid, (byte) d.dataMin, (byte) d.dataMax, applyPhysics))
return true;
}
return false;
}
/**
* Intersects all ItemDatas with all ItemDatas of the given ItemType, returning an ItemType with at most n*m ItemDatas, where n = #ItemDatas of this ItemType, and m =
* #ItemDatas of the argument.
*
* @see ItemData#intersection(ItemData)
* @param other
* @return A new item type which is the intersection of the two item types or null if the intersection is empty.
*/
@Nullable
public ItemType intersection(final ItemType other) {
if (amount != -1 || other.amount != -1 || enchantments != null || other.enchantments != null)
throw new IllegalStateException("ItemType.intersection(ItemType) must only be used to instersect aliases");
final ItemType r = new ItemType();
for (final ItemData d1 : types) {
for (final ItemData d2 : other.types) {
assert d2 != null; // strangely d1 doesn't cause a warning...
r.add_(d1.intersection(d2));
}
}
if (r.types.isEmpty())
return null;
return r;
}
/**
* @param type Some ItemData. Only a copy of it will be stored.
*/
public void add(final @Nullable ItemData type) {
if (type != null)
add_(type.clone());
}
/**
* @param type A cloned or newly created ItemData
*/
private void add_(final @Nullable ItemData type) {
if (type != null) {
types.add(type);
numItems += type.numItems();
modified();
}
}
public void addAll(final Collection<ItemData> types) {
for (final ItemData type : types) {
if (type != null) {
this.types.add(type);
numItems += type.numItems();
}
}
modified();
}
public void remove(final ItemData type) {
if (types.remove(type)) {
numItems -= type.numItems();
modified();
}
}
void remove(final int index) {
final ItemData type = types.remove(index);
numItems -= type.numItems();
modified();
}
public void addEnchantment(final Enchantment e, final int level) {
Map<Enchantment, Integer> enchs = enchantments;
if (enchs == null)
enchantments = enchs = new HashMap<Enchantment, Integer>();
enchs.put(e, level);
}
public void addEnchantments(final Map<Enchantment, Integer> enchantments) {
if (this.enchantments == null)
this.enchantments = new HashMap<Enchantment, Integer>(enchantments);
else
this.enchantments.putAll(enchantments);
}
public void clearEnchantments() {
if (enchantments != null)
enchantments.clear();
}
@Nullable
public Map<Enchantment, Integer> getEnchantments() {
return enchantments;
}
@Override
public Iterator<ItemStack> containerIterator() {
return new Iterator<ItemStack>() {
@SuppressWarnings("null")
Iterator<ItemData> iter = types.iterator();
@Nullable
Iterator<ItemStack> currentDataIter;
@Override
public boolean hasNext() {
Iterator<ItemStack> cdi = currentDataIter;
while (iter.hasNext() && (cdi == null || !cdi.hasNext())) {
currentDataIter = cdi = iter.next().getAll();
}
return cdi != null && cdi.hasNext();
}
@Override
public ItemStack next() {
if (!hasNext())
throw new NoSuchElementException();
final Iterator<ItemStack> cdi = currentDataIter;
if (cdi == null)
throw new NoSuchElementException();
final ItemStack is = cdi.next();
is.setAmount(getAmount());
if (meta != null)
is.setItemMeta(Bukkit.getItemFactory().asMetaFor((ItemMeta) meta, is.getType()));
if (enchantments != null)
is.addUnsafeEnchantments(enchantments);
return is;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Gets all ItemStacks this ItemType represents. Only use this if you know what you're doing, as it returns only one element if this is not an 'every' alias.
*
* @return An Iterable whose iterator will always return the same item(s)
*/
public Iterable<ItemStack> getAll() {
if (!isAll()) {
final ItemStack i = getRandom();
if (i == null)
return EmptyIterable.get();
return new SingleItemIterable<ItemStack>(i);
}
return new Iterable<ItemStack>() {
@Override
public Iterator<ItemStack> iterator() {
return containerIterator();
}
};
}
@Nullable
public ItemStack removeAll(final @Nullable ItemStack item) {
final boolean wasAll = all;
final int oldAmount = amount;
all = true;
amount = -1;
try {
return removeFrom(item);
} finally {
all = wasAll;
amount = oldAmount;
}
}
/**
* Removes this type from the item stack if appropriate
*
* @param item
* @return The passed ItemStack or null if the resulting amount is <= 0
*/
@Nullable
public ItemStack removeFrom(final @Nullable ItemStack item) {
if (item == null)
return null;
if (!isOfType(item))
return item;
if (all && amount == -1)
return null;
final int a = item.getAmount() - getAmount();
if (a <= 0)
return null;
item.setAmount(a);
return item;
}
/**
* Adds this ItemType to the given item stack
*
* @param item
* @return The passed ItemStack or a new one if the passed is null or air
*/
@Nullable
public ItemStack addTo(final @Nullable ItemStack item) {
if (item == null || item.getTypeId() == 0)
return getRandom();
if (isOfType(item))
item.setAmount(Math.min(item.getAmount() + getAmount(), item.getMaxStackSize()));
return item;
}
@Override
public ItemType clone() {
return new ItemType(this);
}
private final static Random random = new Random();
/**
* @return One random ItemStack that this ItemType represents. If you have a List or an Inventory, use {@link #addTo(Inventory)} or {@link #addTo(List)} respectively.
* @see #addTo(Inventory)
* @see #addTo(ItemStack)
* @see #addTo(ItemStack[])
* @see #addTo(List)
* @see #removeFrom(Inventory)
* @see #removeFrom(ItemStack)
* @see #removeFrom(List...)
*/
@Nullable
public ItemStack getRandom() {
if (numItems == 0)
return null;
int item = random.nextInt(numItems);
int i = -1;
while (item >= 0)
item -= types.get(++i).numItems();
final ItemStack is = types.get(i).getRandom();
is.setAmount(getAmount());
if (meta != null)
is.setItemMeta(Bukkit.getItemFactory().asMetaFor((ItemMeta) meta, is.getType()));
if (enchantments != null)
is.addUnsafeEnchantments(enchantments);
return is;
}
/**
* Test whether this ItemType can be put into the given inventory completely.
* <p>
* REMIND If this ItemType represents multiple items with OR, this function will immediately return false.<br/>
* CondCanHold currently blocks aliases without 'every'/'all' as temporary solution.
*
* @param invi
* @return Whether this item type can be added to the given inventory
*/
public boolean hasSpace(final Inventory invi) {
if (!isAll()) {
if (getItem().types.size() != 1 || getItem().types.get(0).hasDataRange() || getItem().types.get(0).typeid == -1)
return false;
}
return addTo(getCopiedContents(invi));
}
public final static ItemStack[] getCopiedContents(final Inventory invi) {
final ItemStack[] buf = invi.getContents();
for (int i = 0; i < buf.length; i++)
if (buf[i] != null)
buf[i] = buf[i].clone();
return buf;
}
/**
* @return List of ItemDatas. The returned list is not modifiable, use {@link #add(ItemData)} and {@link #remove(ItemData)} if you need to change the list, or use the
* {@link #iterator()}.
*/
@SuppressWarnings("null")
public List<ItemData> getTypes() {
return Collections.unmodifiableList(types);
}
public int numTypes() {
return types.size();
}
/**
* @return How many different items this item type represents
*/
public int numItems() {
return numItems;
}
@Override
public Iterator<ItemData> iterator() {
return new Iterator<ItemData>() {
private int next = 0;
@Override
public boolean hasNext() {
return next < types.size();
}
@SuppressWarnings("null")
@Override
public ItemData next() {
if (!hasNext())
throw new NoSuchElementException();
return types.get(next++);
}
@Override
public void remove() {
if (next <= 0)
throw new IllegalStateException();
ItemType.this.remove(--next);
}
};
}
public boolean isContainedIn(final Inventory invi) {
return isContainedIn((Iterable<ItemStack>) invi);
}
public boolean isContainedIn(final Iterable<ItemStack> items) {
for (final ItemData d : types) {
int found = 0;
for (final ItemStack i : items) {
if (d.isOfType(i) && hasMeta(i)) {
found += i == null ? 1 : i.getAmount();
if (found >= getAmount()) {
if (!all)
return true;
break;
}
}
}
if (all && found < getAmount())
return false;
}
return all;
}
public boolean isContainedIn(final ItemStack[] list) {
for (final ItemData d : types) {
int found = 0;
for (final ItemStack i : list) {
if (d.isOfType(i) && hasMeta(i)) {
found += i == null ? 1 : i.getAmount();
if (found >= getAmount()) {
if (!all)
return true;
break;
}
}
}
if (all && found < getAmount())
return false;
}
return all;
}
public boolean removeAll(final Inventory invi) {
final boolean wasAll = all;
final int oldAmount = amount;
all = true;
amount = -1;
try {
return removeFrom(invi);
} finally {
all = wasAll;
amount = oldAmount;
}
}
/**
* Removes this type from the given inventory. Does not call updateInventory for players.
*
* @param invi
* @return Whether everything could be removed from the inventory
*/
public boolean removeFrom(final Inventory invi) {
final ItemStack[] buf = invi.getContents();
final ItemStack[] armour = invi instanceof PlayerInventory ? ((PlayerInventory) invi).getArmorContents() : null;
@SuppressWarnings("unchecked")
final boolean ok = removeFrom(Arrays.asList(buf), armour == null ? null : Arrays.asList(armour));
invi.setContents(buf);
if (armour != null)
((PlayerInventory) invi).setArmorContents(armour);
return ok;
}
public boolean removeAll(final List<ItemStack>... lists) {
final boolean wasAll = all;
final int oldAmount = amount;
all = true;
amount = -1;
try {
return removeFrom(lists);
} finally {
all = wasAll;
amount = oldAmount;
}
}
/**
* @param lists The lists to remove this type from. Each list should implement {@link RandomAccess} or this method will be slow.
* @return Whether this whole item type could be removed (i.e. returns false if the lists didn't contain this item type completely)
*/
public boolean removeFrom(final List<ItemStack>... lists) {
int removed = 0;
boolean ok = true;
for (final ItemData d : types) {
if (all)
removed = 0;
for (final List<ItemStack> list : lists) {
if (list == null)
continue;
assert list instanceof RandomAccess;
for (int i = 0; i < list.size(); i++) {
final ItemStack is = list.get(i);
if (is != null && d.isOfType(is) && hasMeta(is)) {
if (all && amount == -1) {
list.set(i, null);
removed = 1;
continue;
}
final int toRemove = Math.min(is.getAmount(), getAmount() - removed);
removed += toRemove;
if (toRemove == is.getAmount()) {
list.set(i, null);
} else {
is.setAmount(is.getAmount() - toRemove);
}
if (removed == getAmount()) {
if (!all)
return true;
break;
}
}
}
}
if (all)
ok &= removed == getAmount();
}
if (!all)
return false;
return ok;
}
/**
* Adds this ItemType to the given list, without filling existing stacks.
*
* @param list
*/
public void addTo(final List<ItemStack> list) {
if (!isAll()) {
list.add(getItem().getRandom());
return;
}
for (final ItemStack is : getItem().getAll())
list.add(is);
}
/**
* Tries to add this ItemType to the given inventory. Does not call updateInventory for players.
*
* @param invi
* @return Whether everything could be added to the inventory
*/
public boolean addTo(final Inventory invi) {
// important: don't use inventory.add() - it ignores max stack sizes
final ItemStack[] buf = invi.getContents();
if (buf == null)
return false;
final boolean b = addTo(buf);
invi.setContents(buf);
return b;
}
private static boolean addTo(final @Nullable ItemStack is, final ItemStack[] buf) {
if (is == null || is.getTypeId() == 0)
return true;
int added = 0;
for (int i = 0; i < buf.length; i++) {
if (Utils.itemStacksEqual(is, buf[i])) {
final int toAdd = Math.min(buf[i].getMaxStackSize() - buf[i].getAmount(), is.getAmount() - added);
added += toAdd;
buf[i].setAmount(buf[i].getAmount() + toAdd);
if (added == is.getAmount())
return true;
}
}
for (int i = 0; i < buf.length; i++) {
if (buf[i] == null) {
final int toAdd = Math.min(is.getMaxStackSize(), is.getAmount() - added);
added += toAdd;
buf[i] = is.clone();
buf[i].setAmount(toAdd);
if (added == is.getAmount())
return true;
}
}
return false;
}
public boolean addTo(final ItemStack[] buf) {
if (!isAll()) {
return addTo(getItem().getRandom(), buf);
}
boolean ok = true;
for (final ItemStack is : getItem().getAll()) {
ok &= addTo(is, buf);
}
return ok;
}
/**
* Tests whether a given set of ItemTypes is a subset of another set of ItemTypes.
* <p>
* This method works differently that normal set operations, as is e.g. returns true if set == {everything}.
*
* @param set
* @param sub
* @return Whether all item types in <tt>sub</tt> have at least one {@link #isSupertypeOf(ItemType) super type} in <tt>set</tt>
*/
public final static boolean isSubset(final ItemType[] set, final ItemType[] sub) {
outer: for (final ItemType i : sub) {
assert i != null;
for (final ItemType t : set) {
if (t.isSupertypeOf(i))
continue outer;
}
return false;
}
return true;
}
// /**
// * Saves an array of ItemTypes, separated by '|'
// */
// public static String serialize(final ItemType[] types) {
// if (types == null)
// return "";
// final StringBuilder b = new StringBuilder();
// for (final ItemType t : types) {
// if (b.length() != 0)
// b.append("|");
// final Pair<String, String> p = Classes.serialize(t);
// assert p.first.equals("itemtype");
// b.append(p.second.replace("|", "||"));
// }
// return b.toString();
// }
/**
* Loads an array of ItemTypes separated by '|'
*/
@Deprecated
@Nullable
public static ItemType[] deserialize(final String s) {
if (s.isEmpty())
return null;
final String[] split = s.split("(?!<\\|)\\|(?!\\|)");
final ItemType[] types = new ItemType[split.length];
for (int i = 0; i < types.length; i++) {
final ItemType t = (ItemType) Classes.deserialize("itemtype", "" + split[i].replace("||", "|"));
if (t == null)
return null;
types[i] = t;
}
return types;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (all ? 1231 : 1237);
result = prime * result + amount;
final Map<Enchantment, Integer> enchs = enchantments;
result = prime * result + ((enchs == null) ? 0 : enchs.hashCode());
final Object m = meta;
result = prime * result + ((m == null) ? 0 : m.hashCode());
result = prime * result + types.hashCode();
return result;
}
@Override
public boolean equals(final @Nullable Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof ItemType))
return false;
final ItemType other = (ItemType) obj;
if (all != other.all)
return false;
if (amount != other.amount)
return false;
final Map<Enchantment, Integer> enchs = enchantments;
if (enchs == null) {
if (other.enchantments != null)
return false;
} else if (!enchs.equals(other.enchantments))
return false;
final Object m = meta;
if (m == null) {
if (other.meta != null)
return false;
} else if (!m.equals(other.meta))
return false;
if (!types.equals(other.types))
return false;
return true;
}
@Override
public String toString() {
return toString(false, 0, null);
}
@Override
public String toString(final int flags) {
return toString(false, flags, null);
}
public String toString(final int flags, final @Nullable Adjective a) {
return toString(false, flags, a);
}
private String toString(final boolean debug, final int flags, final @Nullable Adjective a) {
final StringBuilder b = new StringBuilder();
// if (types.size() == 1 && !types.get(0).hasDataRange()) {
// if (getAmount() != 1)
// b.append(amount + " ");
// if (isAll())
// b.append(getAmount() == 1 ? "every " : "of every ");
// } else {
// if (getAmount() != 1)
// b.append(amount + " of ");
// b.append(isAll() ? "every " : "any ");
// }
final boolean plural = amount != 1 && amount != -1 || (flags & Language.F_PLURAL) != 0;
if (amount != -1 && amount != 1) {
b.append(amount + " ");
} else {
b.append(Noun.getArticleWithSpace(types.get(0).getGender(), flags));
}
if (a != null)
b.append(a.toString(types.get(0).getGender(), flags));
for (int i = 0; i < types.size(); i++) {
if (i != 0) {// this belongs here as size-1 can be 0
if (i == types.size() - 1)
b.append(" " + (isAll() ? GeneralWords.and : GeneralWords.or) + " ");
else
b.append(", ");
}
b.append(types.get(i).toString(debug, plural));
}
final Map<Enchantment, Integer> enchs = enchantments;
if (enchs == null)
return "" + b.toString();
b.append(Language.getSpaced("enchantments.of"));
int i = 0;
for (final Entry<Enchantment, Integer> e : enchs.entrySet()) {
if (i != 0) {
if (i != enchs.size() - 1)
b.append(", ");
else
b.append(" " + GeneralWords.and + " ");
}
final Enchantment ench = e.getKey();
if (ench == null)
continue;
b.append(EnchantmentType.toString(ench));
b.append(" ");
b.append(e.getValue());
i++;
}
if (meta != null) {
final ItemMeta m = (ItemMeta) meta;
if (m.hasDisplayName()) {
b.append(" " + m_named.toString() + " ");
b.append("\"" + m.getDisplayName() + "\"");
}
if (debug)
b.append(" meta=[").append(meta).append("]");
}
return "" + b.toString();
}
public static String toString(final ItemStack i) {
return new ItemType(i).toString();
}
public static String toString(final ItemStack i, final int flags) {
return new ItemType(i).toString(flags);
}
public String getDebugMessage() {
return toString(true, 0, null);
}
private void writeObject(final ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
final Map<Enchantment, Integer> enchantments = this.enchantments;
if (enchantments == null) {
out.writeObject(null);
} else {
// Integer instead of int to allow nulls
final Integer[][] enchs = new Integer[enchantments.size()][];
int i = 0;
for (final Entry<Enchantment, Integer> e : enchantments.entrySet()) {
enchs[i] = new Integer[] {Integer.valueOf(e.getKey().getId()), e.getValue()};
i++;
}
out.writeObject(enchs);
}
final Object m = meta;
out.writeObject(m == null ? null : ConfigurationSerializer.serializeCS((ItemMeta) m));
}
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
final Object o = in.readObject();
if (o == null)
return;
final Integer[][] enchs = (Integer[][]) o;
enchantments = new HashMap<Enchantment, Integer>(enchs.length);
for (final Integer[] e : enchs)
enchantments.put(Enchantment.getById(e[0]), e[1]);
final String m = (String) in.readObject();
if (m != null)
meta = ConfigurationSerializer.deserializeCS(m, ItemMeta.class);
}
@Override
public Fields serialize() throws NotSerializableException {
final Fields f = new Fields(this);
// both are serialisable with Yggdrasil
f.putObject("enchantments", enchantments);
f.putObject("meta", meta);
return f;
}
@Override
public void deserialize(final Fields fields) throws StreamCorruptedException, NotSerializableException {
enchantments = fields.getAndRemoveObject("enchantments", Map.class);
meta = fields.getAndRemoveObject("meta", Object.class);
if (meta != null && !(meta instanceof ItemMeta))
throw new StreamCorruptedException();
fields.setFields(this);
}
}