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 }