Search code examples
javareplaceminecraftplaceholderbukkit

ItemStack with many placeholders


I am currently developing a minecraft plugin that contains many items with many placeholders.

I have a method that has two parameters: an ItemStack and an array of Strings.

in the array, on the odd positions are the variables, and on the even positions are the values with which they must be replaced.

I would like to know what is the most efficient method to replace those variables.

My code:

public static ItemStack replace(ItemStack item, String... placeholders) {
    ItemMeta meta = item.getItemMeta();
    
    
    List<String> lore1 = meta.getLore();
    
    String lore = serializeLore(lore1);
    String name = meta.getDisplayName();
    
    for(int i = 0; i < placeholders.length; i+=2) {
        lore = lore.replace((String)placeholders[i], (String)placeholders[i + 1]);
        name = name.replace((String)placeholders[i], (String)placeholders[i + 1]);
    }
    
    meta.setLore(Arrays.asList(lore.split("\n")));
    meta.setDisplayName(name);
    
    item.setItemMeta(meta);
    return item;
}

private static String serializeLore(List<String> lore) {
    String result = "";
    
    for(int i = 0; i < lore.size(); i++) {
        result += lore.get(i) + (i + 1 < lore.size() ? "\n" : "");
    }
    return result;
}

For example:

replace(item, "%player%", "John", "%coins%", "2000", "%score%", "80");

Edit: A example of item lore:

"&7Welcome Back &a%player%!"
""
"&7Crazy fun minigames to play with friends:"
"&f - %minigame1%"
"&f - %minigame2%"
"&f - %minigame3%"
"&f - %minigame4%"
"&f - %minigame5%"
"&f - %minigame6%"
"&f - %minigame7%"
"&f - %minigame8%"
""
"&7Coins: &6%coins%"
"&7Score: &2%score%"
"&7Games Played: &9%gamesPlayed%"
""
"&aClick to play!"
"&7%currentPlaying% Currently playing!"


Solution

  • I think the performance could be improved by reducing the number calls to replace. I thought it would be faster to loop through the lore, find each replacement site in each lore by finding strings surrounded by % characters, use that key to look up the display value (placeholders could be a Dictionary instead of array of even/odd pairs), then use a more performant StringBuilder to do your string modifications.

    Consider the following pseudo-code:

    public static ItemStack replace(ItemStack item, Dictionary<String, String> placeholders) {
        ItemMeta meta = item.getItemMeta();
        String lore = String.join("\n", meta.getLore();
        String name = meta.getDisplayName();
    
        String modifiedLore = fillInReplacementValues(lore, placeholders);
        String modifiedName = fillInReplacementValues(name, placeholders);
    
        meta.setLore(modifiedLore.split('\n');
        meta.setDisplayName(name);
        
        item.setItemMeta(meta);
        return item;
    }
    
    public static String fillInReplacementValues(String stringToModify, Dictionary<String, String> placeholders) {
        StringBuilder stringBuilder = new StringBuilder(stringToModify);
        String replacementMarker = "%";
        Boolean replacementMarkerFound = false;
        int replacementEndIndex = 0;
    
        // Start from the end of the lore string and look back
        for(int i = stringBuilder.length() - 1; i >= 0; i--) {
            // Did we find a marker end?
            if (replacementMarker == stringBuilder.charAt(i)) {
                if (replacementMarkerFound) {
                    // We found the start and end indexes of %
                    // Pull the name of the key field (coin, player etc)
                    String replacementName = stringBuilder.substring(i + 1, replacementEndIndex - 1);
                    // replace the placeholder with value (ie %coin% with 1000)
                    stringBuilder.replace(i, replacementEndIndex, placeholders[replacementName]);
                    replacementMarkerFound = false;
                } else {
                    replacementMarkerFound = true;
                    replacementEndIndex = i;
                }
            }
        }
        return stringBuilder.toString();
    }