mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2025-03-13 15:19:59 +01:00
Improve ItemParser code. (#1821)
* Improve ItemParser code. Add ability to parse text if Material is just a single string. Add ability to parse player heads. Add comments to the code. * Fixes failing test.
This commit is contained in:
parent
cf8df9c2a8
commit
56a1fdb55a
@ -1,17 +1,24 @@
|
||||
package world.bentobox.bentobox.util;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.banner.Pattern;
|
||||
import org.bukkit.block.banner.PatternType;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BannerMeta;
|
||||
import org.bukkit.inventory.meta.PotionMeta;
|
||||
import org.bukkit.inventory.meta.*;
|
||||
import org.bukkit.potion.PotionData;
|
||||
import org.bukkit.potion.PotionType;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.UUID;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class that parses a String into an ItemStack.
|
||||
* It is used for converting config file entries to objects.
|
||||
@ -19,64 +26,114 @@ import world.bentobox.bentobox.BentoBox;
|
||||
* @author tastybento, Poslovitch
|
||||
*/
|
||||
public class ItemParser {
|
||||
|
||||
private ItemParser() {}
|
||||
|
||||
public static ItemStack parse(String s){
|
||||
if (s == null) {
|
||||
/**
|
||||
* Parse given string to ItemStack.
|
||||
* @param text String value of item stack.
|
||||
* @return ItemStack of parsed item or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static ItemStack parse(String text) {
|
||||
if (text == null || text.isBlank()) {
|
||||
// Text does not exist or is empty.
|
||||
return null;
|
||||
}
|
||||
String[] part = s.split(":");
|
||||
|
||||
// Material-specific handling
|
||||
if (part[0].contains("POTION") || part[0].equalsIgnoreCase("TIPPED_ARROW")) {
|
||||
return potion(part);
|
||||
} else if (part[0].contains("BANNER")) {
|
||||
return banner(part);
|
||||
String[] part = text.split(":");
|
||||
|
||||
try {
|
||||
// Check if there are more properties for the item stack
|
||||
if (part.length == 1) {
|
||||
// Parse material directly. It does not have any extra properties.
|
||||
return new ItemStack(Material.valueOf(text.toUpperCase()));
|
||||
}
|
||||
// Material-specific handling
|
||||
else if (part[0].contains("POTION") || part[0].equalsIgnoreCase("TIPPED_ARROW")) {
|
||||
// Parse Potions and Tipped Arrows
|
||||
return parsePotion(part);
|
||||
} else if (part[0].contains("BANNER")) {
|
||||
// Parse Banners
|
||||
return parseBanner(part);
|
||||
} else if (part[0].equalsIgnoreCase("PLAYER_HEAD")) {
|
||||
// Parse Player Heads
|
||||
return parsePlayerHead(part);
|
||||
}
|
||||
// Generic handling
|
||||
else if (part.length == 2) {
|
||||
// Material:Qty
|
||||
return parseItemQuantity(part);
|
||||
} else if (part.length == 3) {
|
||||
// Material:Durability:Qty
|
||||
return parseItemDurabilityAndQuantity(part);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
BentoBox.getInstance().logError("Could not parse item " + text);
|
||||
}
|
||||
|
||||
// Generic handling
|
||||
if (part.length == 2) {
|
||||
// Material:Qty
|
||||
return two(part);
|
||||
} else if (part.length == 3) {
|
||||
// Material:Durability:Qty
|
||||
return three(part);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ItemStack two(String[] part) {
|
||||
int reqAmount;
|
||||
try {
|
||||
reqAmount = Integer.parseInt(part[1]);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses array of 2 items into an item stack.
|
||||
* First array element is material, while second array element is integer, that represents item count.
|
||||
* Example:
|
||||
* DIAMOND:20
|
||||
* @param part String array that contains 2 elements.
|
||||
* @return ItemStack with material from first array element and amount based on second array element.
|
||||
*/
|
||||
private static ItemStack parseItemQuantity(String[] part) {
|
||||
int reqAmount = Integer.parseInt(part[1]);
|
||||
Material reqItem = Material.getMaterial(part[0].toUpperCase(java.util.Locale.ENGLISH));
|
||||
|
||||
if (reqItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ItemStack(reqItem, reqAmount);
|
||||
}
|
||||
|
||||
private static ItemStack three(String[] part) {
|
||||
|
||||
/**
|
||||
* This method parses array of 3 items into an item stack.
|
||||
* First array element is material, while second and third array element are integers.
|
||||
* The middle element represents durability, while third element represents quantity.
|
||||
* Example:
|
||||
* IRON_SWORD:20:1
|
||||
* @param part String array that contains 3 elements.
|
||||
* @return ItemStack with material from first array element, durability from second element and amount based on third array element.
|
||||
*/
|
||||
private static ItemStack parseItemDurabilityAndQuantity(String[] part) {
|
||||
// Rearrange
|
||||
String[] twoer = {part[0], part[2]};
|
||||
return two(twoer);
|
||||
String[] parsable = {part[0], part[2]};
|
||||
ItemStack durability = parseItemQuantity(parsable);
|
||||
|
||||
if (durability != null) {
|
||||
ItemMeta meta = durability.getItemMeta();
|
||||
|
||||
if (meta instanceof Damageable) {
|
||||
((Damageable) meta).setDamage(Integer.parseInt(part[1]));
|
||||
durability.setItemMeta(meta);
|
||||
}
|
||||
}
|
||||
|
||||
return durability;
|
||||
}
|
||||
|
||||
private static ItemStack potion(String[] part) {
|
||||
|
||||
/**
|
||||
* This method parses array of 6 items into an item stack.
|
||||
* Format:
|
||||
* POTION:NAME:<LEVEL>:<EXTENDED>:<SPLASH/LINGER>:QTY
|
||||
* Example:
|
||||
* POTION:STRENGTH:1:EXTENDED:SPLASH:1
|
||||
* @param part String array that contains 6 elements.
|
||||
* @return Potion with given properties.
|
||||
*/
|
||||
private static ItemStack parsePotion(String[] part) {
|
||||
if (part.length != 6) {
|
||||
return null;
|
||||
}
|
||||
int reqAmount;
|
||||
try {
|
||||
reqAmount = Integer.parseInt(part[5]);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* # Format POTION:NAME:<LEVEL>:<EXTENDED>:<SPLASH/LINGER>:QTY
|
||||
# LEVEL, EXTENDED, SPLASH, LINGER are optional.
|
||||
@ -104,11 +161,17 @@ public class ItemParser {
|
||||
PotionData data = new PotionData(type, isExtended, isUpgraded);
|
||||
potionMeta.setBasePotionData(data);
|
||||
|
||||
result.setAmount(reqAmount);
|
||||
result.setAmount(Integer.parseInt(part[5]));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ItemStack banner(String[] part) {
|
||||
|
||||
/**
|
||||
* This method parses array of multiple elements for the Banner.
|
||||
* @param part String array that contains at least 2 elements.
|
||||
* @return Banner as item stack.
|
||||
*/
|
||||
private static ItemStack parseBanner(String[] part) {
|
||||
try {
|
||||
if (part.length >= 2) {
|
||||
Material bannerMat = Material.getMaterial(part[0]);
|
||||
@ -134,4 +197,89 @@ public class ItemParser {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method parses array of 2 to 3 elements that represents player head.
|
||||
* Format:
|
||||
* PLAYER_HEAD:<STRING/Trimmed UUID/UUID/Texture>:QTY
|
||||
* PLAYER_HEAD:<STRING/Trimmed UUID/UUID/Texture>
|
||||
* PLAYER_HEAD:QTY
|
||||
* Example:
|
||||
* PLAYER_HEAD:1
|
||||
* PLAYER_HEAD:BONNe1704
|
||||
* PLAYER_HEAD:eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWY1ZjE1OTg4NmNjNTMxZmZlYTBkOGFhNWY5MmVkNGU1ZGE2NWY3MjRjMDU3MGFmODZhOTBiZjAwYzY3YzQyZSJ9fX0:1
|
||||
* @param part String array that contains at least 2 elements.
|
||||
* @return Player head with given properties.
|
||||
*/
|
||||
private static ItemStack parsePlayerHead(String[] part) {
|
||||
ItemStack playerHead;
|
||||
|
||||
if (part.length == 3) {
|
||||
String[] parsable = {part[0], part[2]};
|
||||
// create parse item and quantity.
|
||||
playerHead = parseItemQuantity(parsable);
|
||||
} else if (isNumeric(part[1])) {
|
||||
// there is no meta item for player head.
|
||||
return parseItemQuantity(part);
|
||||
} else {
|
||||
// create new player head item stack.
|
||||
playerHead = new ItemStack(Material.PLAYER_HEAD);
|
||||
}
|
||||
|
||||
if (playerHead == null) {
|
||||
// just a null-pointer check.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set correct Skull texture
|
||||
try {
|
||||
SkullMeta meta = (SkullMeta) playerHead.getItemMeta();
|
||||
|
||||
if (part[1].length() < 17) {
|
||||
// Minecraft player names are in length between 3 and 16 chars.
|
||||
meta.setOwner(part[1]);
|
||||
} else if (part[1].length() == 32) {
|
||||
// trimmed UUID length are 32 chars.
|
||||
meta.setOwningPlayer(Bukkit.getOfflinePlayer(
|
||||
UUID.fromString(part[1].replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"))));
|
||||
} else if (part[1].length() == 36) {
|
||||
// full UUID length are 36 chars.
|
||||
meta.setOwningPlayer(Bukkit.getOfflinePlayer(UUID.fromString(part[1])));
|
||||
} else {
|
||||
// If chars are more than 36, apparently it is base64 encoded texture.
|
||||
GameProfile profile = new GameProfile(UUID.randomUUID(), "");
|
||||
profile.getProperties().put("textures", new Property("textures", part[1]));
|
||||
|
||||
// Null pointer will be caught and ignored.
|
||||
Field profileField = meta.getClass().getDeclaredField("profile");
|
||||
profileField.setAccessible(true);
|
||||
profileField.set(meta, profile);
|
||||
}
|
||||
|
||||
// Apply new meta to the item.
|
||||
playerHead.setItemMeta(meta);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
return playerHead;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if given sting is an integer.
|
||||
* @param string Value that must be checked.
|
||||
* @return {@code true} if value is integer, {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isNumeric(String string) {
|
||||
if(string == null || string.equals("")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Integer.parseInt(string);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,11 @@ import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import world.bentobox.bentobox.BentoBox;
|
||||
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({Bukkit.class})
|
||||
@PrepareForTest({BentoBox.class, Bukkit.class})
|
||||
public class ItemParserTest {
|
||||
|
||||
private PotionMeta potionMeta;
|
||||
@ -37,8 +40,10 @@ public class ItemParserTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
PowerMockito.mockStatic(Bukkit.class);
|
||||
PowerMockito.mockStatic(BentoBox.class);
|
||||
ItemFactory itemFactory = mock(ItemFactory.class);
|
||||
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
|
||||
when(BentoBox.getInstance()).thenReturn(mock(BentoBox.class));
|
||||
potionMeta = mock(PotionMeta.class);
|
||||
/*
|
||||
when(itemFactory.getItemMeta(Mockito.eq(Material.POTION))).thenReturn(potionMeta);
|
||||
|
Loading…
Reference in New Issue
Block a user