001 package org.bukkit.inventory;
002
003 import com.google.common.collect.ImmutableMap;
004 import java.util.LinkedHashMap;
005 import java.util.Map;
006
007 import org.apache.commons.lang.Validate;
008 import org.bukkit.Bukkit;
009 import org.bukkit.Material;
010 import org.bukkit.Utility;
011 import org.bukkit.configuration.serialization.ConfigurationSerializable;
012 import org.bukkit.enchantments.Enchantment;
013 import org.bukkit.inventory.meta.ItemMeta;
014 import org.bukkit.material.MaterialData;
015
016 /**
017 * Represents a stack of items
018 */
019 public class ItemStack implements Cloneable, ConfigurationSerializable {
020 private int type = 0;
021 private int amount = 0;
022 private MaterialData data = null;
023 private short durability = 0;
024 private ItemMeta meta;
025
026 @Utility
027 protected ItemStack() {}
028
029 /**
030 * Defaults stack size to 1, with no extra data
031 *
032 * @param type item material id
033 * @deprecated Magic value
034 */
035 @Deprecated
036 public ItemStack(final int type) {
037 this(type, 1);
038 }
039
040 /**
041 * Defaults stack size to 1, with no extra data
042 *
043 * @param type item material
044 */
045 public ItemStack(final Material type) {
046 this(type, 1);
047 }
048
049 /**
050 * An item stack with no extra data
051 *
052 * @param type item material id
053 * @param amount stack size
054 * @deprecated Magic value
055 */
056 @Deprecated
057 public ItemStack(final int type, final int amount) {
058 this(type, amount, (short) 0);
059 }
060
061 /**
062 * An item stack with no extra data
063 *
064 * @param type item material
065 * @param amount stack size
066 */
067 public ItemStack(final Material type, final int amount) {
068 this(type.getId(), amount);
069 }
070
071 /**
072 * An item stack with the specified damage / durability
073 *
074 * @param type item material id
075 * @param amount stack size
076 * @param damage durability / damage
077 * @deprecated Magic value
078 */
079 @Deprecated
080 public ItemStack(final int type, final int amount, final short damage) {
081 this.type = type;
082 this.amount = amount;
083 this.durability = damage;
084 }
085
086 /**
087 * An item stack with the specified damage / durabiltiy
088 *
089 * @param type item material
090 * @param amount stack size
091 * @param damage durability / damage
092 */
093 public ItemStack(final Material type, final int amount, final short damage) {
094 this(type.getId(), amount, damage);
095 }
096
097 /**
098 * @deprecated this method uses an ambiguous data byte object
099 */
100 @Deprecated
101 public ItemStack(final int type, final int amount, final short damage, final Byte data) {
102 this.type = type;
103 this.amount = amount;
104 this.durability = damage;
105 if (data != null) {
106 createData(data);
107 this.durability = data;
108 }
109 }
110
111 /**
112 * @deprecated this method uses an ambiguous data byte object
113 */
114 @Deprecated
115 public ItemStack(final Material type, final int amount, final short damage, final Byte data) {
116 this(type.getId(), amount, damage, data);
117 }
118
119 /**
120 * Creates a new item stack derived from the specified stack
121 *
122 * @param stack the stack to copy
123 * @throws IllegalArgumentException if the specified stack is null or
124 * returns an item meta not created by the item factory
125 */
126 public ItemStack(final ItemStack stack) throws IllegalArgumentException {
127 Validate.notNull(stack, "Cannot copy null stack");
128 this.type = stack.getTypeId();
129 this.amount = stack.getAmount();
130 this.durability = stack.getDurability();
131 this.data = stack.getData();
132 if (stack.hasItemMeta()) {
133 setItemMeta0(stack.getItemMeta(), getType0());
134 }
135 }
136
137 /**
138 * Gets the type of this item
139 *
140 * @return Type of the items in this stack
141 */
142 @Utility
143 public Material getType() {
144 return getType0(getTypeId());
145 }
146
147 private Material getType0() {
148 return getType0(this.type);
149 }
150
151 private static Material getType0(int id) {
152 Material material = Material.getMaterial(id);
153 return material == null ? Material.AIR : material;
154 }
155
156 /**
157 * Sets the type of this item
158 * <p>
159 * Note that in doing so you will reset the MaterialData for this stack
160 *
161 * @param type New type to set the items in this stack to
162 */
163 @Utility
164 public void setType(Material type) {
165 Validate.notNull(type, "Material cannot be null");
166 setTypeId(type.getId());
167 }
168
169 /**
170 * Gets the type id of this item
171 *
172 * @return Type Id of the items in this stack
173 * @deprecated Magic value
174 */
175 @Deprecated
176 public int getTypeId() {
177 return type;
178 }
179
180 /**
181 * Sets the type id of this item
182 * <p>
183 * Note that in doing so you will reset the MaterialData for this stack
184 *
185 * @param type New type id to set the items in this stack to
186 * @deprecated Magic value
187 */
188 @Deprecated
189 public void setTypeId(int type) {
190 this.type = type;
191 if (this.meta != null) {
192 this.meta = Bukkit.getItemFactory().asMetaFor(meta, getType0());
193 }
194 createData((byte) 0);
195 }
196
197 /**
198 * Gets the amount of items in this stack
199 *
200 * @return Amount of items in this stick
201 */
202 public int getAmount() {
203 return amount;
204 }
205
206 /**
207 * Sets the amount of items in this stack
208 *
209 * @param amount New amount of items in this stack
210 */
211 public void setAmount(int amount) {
212 this.amount = amount;
213 }
214
215 /**
216 * Gets the MaterialData for this stack of items
217 *
218 * @return MaterialData for this item
219 */
220 public MaterialData getData() {
221 Material mat = getType();
222 if (data == null && mat != null && mat.getData() != null) {
223 data = mat.getNewData((byte) this.getDurability());
224 }
225
226 return data;
227 }
228
229 /**
230 * Sets the MaterialData for this stack of items
231 *
232 * @param data New MaterialData for this item
233 */
234 public void setData(MaterialData data) {
235 Material mat = getType();
236
237 if (data == null || mat == null || mat.getData() == null) {
238 this.data = data;
239 } else {
240 if ((data.getClass() == mat.getData()) || (data.getClass() == MaterialData.class)) {
241 this.data = data;
242 } else {
243 throw new IllegalArgumentException("Provided data is not of type " + mat.getData().getName() + ", found " + data.getClass().getName());
244 }
245 }
246 }
247
248 /**
249 * Sets the durability of this item
250 *
251 * @param durability Durability of this item
252 */
253 public void setDurability(final short durability) {
254 this.durability = durability;
255 }
256
257 /**
258 * Gets the durability of this item
259 *
260 * @return Durability of this item
261 */
262 public short getDurability() {
263 return durability;
264 }
265
266 /**
267 * Get the maximum stacksize for the material hold in this ItemStack.
268 * (Returns -1 if it has no idea)
269 *
270 * @return The maximum you can stack this material to.
271 */
272 @Utility
273 public int getMaxStackSize() {
274 Material material = getType();
275 if (material != null) {
276 return material.getMaxStackSize();
277 }
278 return -1;
279 }
280
281 private void createData(final byte data) {
282 Material mat = Material.getMaterial(type);
283
284 if (mat == null) {
285 this.data = new MaterialData(type, data);
286 } else {
287 this.data = mat.getNewData(data);
288 }
289 }
290
291 @Override
292 @Utility
293 public String toString() {
294 StringBuilder toString = new StringBuilder("ItemStack{").append(getType().name()).append(" x ").append(getAmount());
295 if (hasItemMeta()) {
296 toString.append(", ").append(getItemMeta());
297 }
298 return toString.append('}').toString();
299 }
300
301 @Override
302 @Utility
303 public boolean equals(Object obj) {
304 if (this == obj) {
305 return true;
306 }
307 if (!(obj instanceof ItemStack)) {
308 return false;
309 }
310
311 ItemStack stack = (ItemStack) obj;
312 return getAmount() == stack.getAmount() && isSimilar(stack);
313 }
314
315 /**
316 * This method is the same as equals, but does not consider stack size
317 * (amount).
318 *
319 * @param stack the item stack to compare to
320 * @return true if the two stacks are equal, ignoring the amount
321 */
322 @Utility
323 public boolean isSimilar(ItemStack stack) {
324 if (stack == null) {
325 return false;
326 }
327 if (stack == this) {
328 return true;
329 }
330 return getTypeId() == stack.getTypeId() && getDurability() == stack.getDurability() && hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true);
331 }
332
333 @Override
334 public ItemStack clone() {
335 try {
336 ItemStack itemStack = (ItemStack) super.clone();
337
338 if (this.meta != null) {
339 itemStack.meta = this.meta.clone();
340 }
341
342 if (this.data != null) {
343 itemStack.data = this.data.clone();
344 }
345
346 return itemStack;
347 } catch (CloneNotSupportedException e) {
348 throw new Error(e);
349 }
350 }
351
352 @Override
353 @Utility
354 public final int hashCode() {
355 int hash = 1;
356
357 hash = hash * 31 + getTypeId();
358 hash = hash * 31 + getAmount();
359 hash = hash * 31 + (getDurability() & 0xffff);
360 hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0);
361
362 return hash;
363 }
364
365 /**
366 * Checks if this ItemStack contains the given {@link Enchantment}
367 *
368 * @param ench Enchantment to test
369 * @return True if this has the given enchantment
370 */
371 public boolean containsEnchantment(Enchantment ench) {
372 return meta == null ? false : meta.hasEnchant(ench);
373 }
374
375 /**
376 * Gets the level of the specified enchantment on this item stack
377 *
378 * @param ench Enchantment to check
379 * @return Level of the enchantment, or 0
380 */
381 public int getEnchantmentLevel(Enchantment ench) {
382 return meta == null ? 0 : meta.getEnchantLevel(ench);
383 }
384
385 /**
386 * Gets a map containing all enchantments and their levels on this item.
387 *
388 * @return Map of enchantments.
389 */
390 public Map<Enchantment, Integer> getEnchantments() {
391 return meta == null ? ImmutableMap.<Enchantment, Integer>of() : meta.getEnchants();
392 }
393
394 /**
395 * Adds the specified enchantments to this item stack.
396 * <p>
397 * This method is the same as calling {@link
398 * #addEnchantment(org.bukkit.enchantments.Enchantment, int)} for each
399 * element of the map.
400 *
401 * @param enchantments Enchantments to add
402 * @throws IllegalArgumentException if the specified enchantments is null
403 * @throws IllegalArgumentException if any specific enchantment or level
404 * is null. <b>Warning</b>: Some enchantments may be added before this
405 * exception is thrown.
406 */
407 @Utility
408 public void addEnchantments(Map<Enchantment, Integer> enchantments) {
409 Validate.notNull(enchantments, "Enchantments cannot be null");
410 for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
411 addEnchantment(entry.getKey(), entry.getValue());
412 }
413 }
414
415 /**
416 * Adds the specified {@link Enchantment} to this item stack.
417 * <p>
418 * If this item stack already contained the given enchantment (at any
419 * level), it will be replaced.
420 *
421 * @param ench Enchantment to add
422 * @param level Level of the enchantment
423 * @throws IllegalArgumentException if enchantment null, or enchantment is
424 * not applicable
425 */
426 @Utility
427 public void addEnchantment(Enchantment ench, int level) {
428 Validate.notNull(ench, "Enchantment cannot be null");
429 if ((level < ench.getStartLevel()) || (level > ench.getMaxLevel())) {
430 throw new IllegalArgumentException("Enchantment level is either too low or too high (given " + level + ", bounds are " + ench.getStartLevel() + " to " + ench.getMaxLevel() + ")");
431 } else if (!ench.canEnchantItem(this)) {
432 throw new IllegalArgumentException("Specified enchantment cannot be applied to this itemstack");
433 }
434
435 addUnsafeEnchantment(ench, level);
436 }
437
438 /**
439 * Adds the specified enchantments to this item stack in an unsafe manner.
440 * <p>
441 * This method is the same as calling {@link
442 * #addUnsafeEnchantment(org.bukkit.enchantments.Enchantment, int)} for
443 * each element of the map.
444 *
445 * @param enchantments Enchantments to add
446 */
447 @Utility
448 public void addUnsafeEnchantments(Map<Enchantment, Integer> enchantments) {
449 for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
450 addUnsafeEnchantment(entry.getKey(), entry.getValue());
451 }
452 }
453
454 /**
455 * Adds the specified {@link Enchantment} to this item stack.
456 * <p>
457 * If this item stack already contained the given enchantment (at any
458 * level), it will be replaced.
459 * <p>
460 * This method is unsafe and will ignore level restrictions or item type.
461 * Use at your own discretion.
462 *
463 * @param ench Enchantment to add
464 * @param level Level of the enchantment
465 */
466 public void addUnsafeEnchantment(Enchantment ench, int level) {
467 (meta == null ? meta = Bukkit.getItemFactory().getItemMeta(getType0()) : meta).addEnchant(ench, level, true);
468 }
469
470 /**
471 * Removes the specified {@link Enchantment} if it exists on this
472 * ItemStack
473 *
474 * @param ench Enchantment to remove
475 * @return Previous level, or 0
476 */
477 public int removeEnchantment(Enchantment ench) {
478 int level = getEnchantmentLevel(ench);
479 if (level == 0 || meta == null) {
480 return level;
481 }
482 meta.removeEnchant(ench);
483 return level;
484 }
485
486 @Utility
487 public Map<String, Object> serialize() {
488 Map<String, Object> result = new LinkedHashMap<String, Object>();
489
490 result.put("type", getType().name());
491
492 if (getDurability() != 0) {
493 result.put("damage", getDurability());
494 }
495
496 if (getAmount() != 1) {
497 result.put("amount", getAmount());
498 }
499
500 ItemMeta meta = getItemMeta();
501 if (!Bukkit.getItemFactory().equals(meta, null)) {
502 result.put("meta", meta);
503 }
504
505 return result;
506 }
507
508 /**
509 * Required method for configuration serialization
510 *
511 * @param args map to deserialize
512 * @return deserialized item stack
513 * @see ConfigurationSerializable
514 */
515 public static ItemStack deserialize(Map<String, Object> args) {
516 Material type = Material.getMaterial((String) args.get("type"));
517 short damage = 0;
518 int amount = 1;
519
520 if (args.containsKey("damage")) {
521 damage = ((Number) args.get("damage")).shortValue();
522 }
523
524 if (args.containsKey("amount")) {
525 amount = (Integer) args.get("amount");
526 }
527
528 ItemStack result = new ItemStack(type, amount, damage);
529
530 if (args.containsKey("enchantments")) { // Backward compatiblity, @deprecated
531 Object raw = args.get("enchantments");
532
533 if (raw instanceof Map) {
534 Map<?, ?> map = (Map<?, ?>) raw;
535
536 for (Map.Entry<?, ?> entry : map.entrySet()) {
537 Enchantment enchantment = Enchantment.getByName(entry.getKey().toString());
538
539 if ((enchantment != null) && (entry.getValue() instanceof Integer)) {
540 result.addUnsafeEnchantment(enchantment, (Integer) entry.getValue());
541 }
542 }
543 }
544 } else if (args.containsKey("meta")) { // We cannot and will not have meta when enchantments (pre-ItemMeta) exist
545 Object raw = args.get("meta");
546 if (raw instanceof ItemMeta) {
547 result.setItemMeta((ItemMeta) raw);
548 }
549 }
550
551 return result;
552 }
553
554 /**
555 * Get a copy of this ItemStack's {@link ItemMeta}.
556 *
557 * @return a copy of the current ItemStack's ItemData
558 */
559 public ItemMeta getItemMeta() {
560 return this.meta == null ? Bukkit.getItemFactory().getItemMeta(getType0()) : this.meta.clone();
561 }
562
563 /**
564 * Checks to see if any meta data has been defined.
565 *
566 * @return Returns true if some meta data has been set for this item
567 */
568 public boolean hasItemMeta() {
569 return !Bukkit.getItemFactory().equals(meta, null);
570 }
571
572 /**
573 * Set the ItemMeta of this ItemStack.
574 *
575 * @param itemMeta new ItemMeta, or null to indicate meta data be cleared.
576 * @return True if successfully applied ItemMeta, see {@link
577 * ItemFactory#isApplicable(ItemMeta, ItemStack)}
578 * @throws IllegalArgumentException if the item meta was not created by
579 * the {@link ItemFactory}
580 */
581 public boolean setItemMeta(ItemMeta itemMeta) {
582 return setItemMeta0(itemMeta, getType0());
583 }
584
585 /*
586 * Cannot be overridden, so it's safe for constructor call
587 */
588 private boolean setItemMeta0(ItemMeta itemMeta, Material material) {
589 if (itemMeta == null) {
590 this.meta = null;
591 return true;
592 }
593 if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) {
594 return false;
595 }
596 this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material);
597 if (this.meta == itemMeta) {
598 this.meta = itemMeta.clone();
599 }
600
601 return true;
602 }
603 }