From 2607256c060667032fe2d75a02ed4aedd0193134 Mon Sep 17 00:00:00 2001 From: BONNe Date: Mon, 30 Aug 2021 00:34:05 +0300 Subject: [PATCH] Panel template (#1841) * Implement basic functionality to read data from template panels. Create TemplateReader class that has static method which generates a PanelTemplateRecord. This record contains every necessary information from user created template file so everyone could use it to generate a functional panel. These classes are just for reading templates and do not create actual panel. * Add template clearing via bentobox reload command. --- .../api/panels/reader/ItemTemplateRecord.java | 87 +++++ .../panels/reader/PanelTemplateRecord.java | 74 ++++ .../api/panels/reader/TemplateReader.java | 324 ++++++++++++++++++ .../commands/BentoBoxReloadCommand.java | 3 + 4 files changed, 488 insertions(+) create mode 100644 src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java create mode 100644 src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java create mode 100644 src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java new file mode 100644 index 000000000..2338171e5 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/ItemTemplateRecord.java @@ -0,0 +1,87 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.bentobox.api.panels.reader; + + +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * This Record contains all necessary information about Item Template that can be used to craft panel item. + * + * @param icon ItemStack of the Item + * @param title Title of the item + * @param description Lore message of the item + * @param actions List of Actions for a button + * @param dataMap DataMap that links additional objects for a button. + * @param fallback FallBack item if current one is not possible to generate. + * + * @since 1.17.3 + */ +public record ItemTemplateRecord(ItemStack icon, + String title, + String description, + List actions, + Map dataMap, + ItemTemplateRecord fallback) +{ + /** + * Instantiates a new Item template record without actions and data map. + * + * @param icon the icon + * @param title the title + * @param description the description + * @param fallback the fallback + */ + public ItemTemplateRecord(ItemStack icon, String title, String description, ItemTemplateRecord fallback) + { + this(icon, title, description, new ArrayList<>(6), new HashMap<>(0), fallback); + } + + + /** + * This method adds given object associated with key into data map. + * @param key Key value of object. + * @param data Data that is associated with a key. + */ + public void addData(String key, Object data) + { + this.dataMap.put(key, data); + } + + + /** + * Add action to the actions list. + * + * @param actionData the action data + */ + public void addAction(ActionRecords actionData) + { + this.actions.add(actionData); + } + + + // --------------------------------------------------------------------- + // Section: Classes + // --------------------------------------------------------------------- + + + /** + * The Action Records holds data about each action. + * + * @param clickType the click type + * @param actionType the string that represents action type + * @param content the content of the action + * @param tooltip the tooltip of action + */ + public record ActionRecords(ClickType clickType, String actionType, String content, String tooltip) {} +} diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java new file mode 100644 index 000000000..22f8f6767 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/PanelTemplateRecord.java @@ -0,0 +1,74 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.bentobox.api.panels.reader; + + +import org.bukkit.inventory.ItemStack; + +import world.bentobox.bentobox.api.panels.Panel; + + +/** + * This is template object for the panel reader. It contains data that can exist in the panel. + * PanelBuilder will use this to build panel. + * + * @param type the type of GUI + * @param title the title of GUI + * @param border the border block for GUI + * @param background the background block for GUI. + * @param content The 2D array of ItemTemplateRecords + * + * @since 1.17.3 + */ +public record PanelTemplateRecord(Panel.Type type, + String title, + TemplateItem border, + TemplateItem background, + ItemTemplateRecord[][] content) +{ + /** + * Instantiates a new Panel template record with empty content. + * + * @param type the type + * @param title the title + * @param border the border + * @param background the background + */ + public PanelTemplateRecord(Panel.Type type, String title, TemplateItem border, TemplateItem background) + { + this(type, title, border, background, new ItemTemplateRecord[6][9]); + } + + + /** + * This method adds give item template record in given slot. + * @param rowIndex row index of content array + * @param columnIndex column index of content array. + * @param panelItemTemplate item template record that must be added. + */ + public void addButtonTemplate(int rowIndex, int columnIndex, ItemTemplateRecord panelItemTemplate) + { + this.content[rowIndex][columnIndex] = panelItemTemplate; + } + + + // --------------------------------------------------------------------- + // Section: Classes + // --------------------------------------------------------------------- + + + /** + * This record contains info about border and background item. + */ + public record TemplateItem(ItemStack icon, String title, String description) + { + public TemplateItem(ItemStack icon) + { + this(icon, null, null); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java new file mode 100644 index 000000000..27b6b9f67 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java @@ -0,0 +1,324 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +package world.bentobox.bentobox.api.panels.reader; + + +import com.google.common.base.Enums; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.inventory.ClickType; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.util.ItemParser; + + +/** + * This class manages Template file reading, creating PanelTemplateRecord object and storing it internally. + * This class just reads and returns given panel template. It does not create a functional panel. + * + * @since 1.17.3 + */ +public class TemplateReader +{ + /** + * Read template panel panel template record. + * + * @param panelName the panel name + * @param panelLocation the panel location directory + * @return the panel template record + */ + public static PanelTemplateRecord readTemplatePanel(@NonNull String panelName, @NonNull File panelLocation) + { + if (!panelLocation.exists()) + { + // Return null because folder does not exist. + return null; + } + + File file = new File(panelLocation, panelName.endsWith(".yml") ? panelName : panelName + ".yml"); + + if (!file.exists()) + { + // Return as file does not exist. + return null; + } + + // Check if panel is already crafted. + if (TemplateReader.loadedPanels.containsKey(file.getAbsolutePath())) + { + return TemplateReader.loadedPanels.get(file.getAbsolutePath()); + } + + PanelTemplateRecord record; + + try + { + // Load config + YamlConfiguration config = new YamlConfiguration(); + config.load(file); + // Read panel + record = readPanelTemplate(config.getConfigurationSection(panelName)); + // Put panel into memory + TemplateReader.loadedPanels.put(file.getAbsolutePath(), record); + } + catch (IOException | InvalidConfigurationException e) + { + record = null; + } + + return record; + } + + + /** + * This method reads panel template from given configuration section. + * @param configurationSection Section that contains panel template data. + * @return Panel Template. + */ + private static PanelTemplateRecord readPanelTemplate(@Nullable ConfigurationSection configurationSection) + { + if (configurationSection == null) + { + // No data to return. + return null; + } + + String title = configurationSection.getString("title"); + Panel.Type type = configurationSection.getObject("type", Panel.Type.class); + + PanelTemplateRecord.TemplateItem borderItem = null; + + // Read Border Icon. + if (configurationSection.isConfigurationSection("border")) + { + // Process border icon if it contains more options. + ConfigurationSection borderSection = configurationSection.getConfigurationSection("border"); + + if (borderSection != null) + { + borderItem = new PanelTemplateRecord.TemplateItem( + ItemParser.parse((borderSection.getString("icon", Material.AIR.name()))), + borderSection.getString("name", null), + borderSection.getString("description", null)); + } + } + else if (configurationSection.isString("border")) + { + // Process border icon if it contains only icon. + + borderItem = new PanelTemplateRecord.TemplateItem( + ItemParser.parse((configurationSection.getString("border", Material.AIR.name())))); + } + + PanelTemplateRecord.TemplateItem backgroundItem = null; + + // Read Background block + if (configurationSection.isConfigurationSection("background")) + { + // Process border icon if it contains more options. + ConfigurationSection backgroundSection = configurationSection.getConfigurationSection("background"); + + if (backgroundSection != null) + { + backgroundItem = new PanelTemplateRecord.TemplateItem( + ItemParser.parse((backgroundSection.getString("icon", Material.AIR.name()))), + backgroundSection.getString("name", null), + backgroundSection.getString("description", null)); + } + } + else if (configurationSection.isString("background")) + { + // Process background icon if it contains only icon. + + backgroundItem = new PanelTemplateRecord.TemplateItem( + ItemParser.parse((configurationSection.getString("background", Material.AIR.name())))); + } + + // Read reusable + Map panelItemDataMap = new HashMap<>(); + ConfigurationSection reusable = configurationSection.getConfigurationSection("reusable"); + + if (reusable != null) + { + // Add all reusables to the local storage. + reusable.getKeys(false).forEach(key -> + readPanelItemTemplate(reusable.getConfigurationSection(key), key, panelItemDataMap)); + } + + // Create template record. + PanelTemplateRecord template = new PanelTemplateRecord(type, title, borderItem, backgroundItem); + + // Read content + ConfigurationSection content = configurationSection.getConfigurationSection("content"); + + if (content == null) + { + // Return empty template. + return template; + } + + for (int rowIndex = 0; rowIndex < 6; rowIndex++) + { + // Read each line. + if (content.isConfigurationSection(String.valueOf(rowIndex + 1))) + { + ConfigurationSection line = content.getConfigurationSection(String.valueOf(rowIndex + 1)); + + if (line != null) + { + // Populate existing lines with items. + for (int columnIndex = 0; columnIndex < 9; columnIndex++) + { + if (line.isConfigurationSection(String.valueOf(columnIndex + 1))) + { + // If it contains a section, then build a new button template from it. + template.addButtonTemplate(rowIndex, + columnIndex, + readPanelItemTemplate(line.getConfigurationSection(String.valueOf(columnIndex + 1)))); + } + else if (line.isString(String.valueOf(columnIndex + 1))) + { + // If it contains just a single word, assume it is a reusable. + template.addButtonTemplate(rowIndex, + columnIndex, + panelItemDataMap.get(line.getString(String.valueOf(columnIndex + 1)))); + } + } + } + } + } + + // Garbage collector. + panelItemDataMap.clear(); + + return template; + } + + + /** + * This method creates PanelItemTemplate from a given configuration section. + * @param section Section that should contain all information about the panel item template. + * @return PanelItemTemplate that should represent button from a section. + */ + @Nullable + private static ItemTemplateRecord readPanelItemTemplate(@Nullable ConfigurationSection section) + { + return readPanelItemTemplate(section, null, null); + } + + + + /** + * This method creates PanelItemTemplate from a given configuration section. + * @param section Section that should contain all information about the panel item template. + * @return PanelItemTemplate that should represent button from a section. + */ + @Nullable + private static ItemTemplateRecord readPanelItemTemplate(@Nullable ConfigurationSection section, + String itemKey, + Map reusableItemMap) + { + if (section == null) + { + // No section, no item. + return null; + } + + ItemTemplateRecord fallback; + + if (section.isConfigurationSection("fallback")) + { + fallback = readPanelItemTemplate(section.getConfigurationSection("fallback")); + } + else if (section.isString("fallback") && reusableItemMap != null) + { + fallback = reusableItemMap.get(section.getString("fallback")); + } + else + { + fallback = null; + } + + // Create Item Record + ItemTemplateRecord itemRecord = new ItemTemplateRecord(ItemParser.parse(section.getString("icon")), + section.getString("title", null), + section.getString("description", null), + fallback); + + // Read data + if (section.isConfigurationSection("data")) + { + ConfigurationSection dataSection = section.getConfigurationSection("data"); + + if (dataSection != null) + { + dataSection.getKeys(false).forEach(key -> itemRecord.addData(key, dataSection.get(key))); + } + } + + // Read Click data + if (section.isConfigurationSection("actions")) + { + ConfigurationSection actionSection = section.getConfigurationSection("actions"); + + if (actionSection != null) + { + actionSection.getKeys(false).forEach(actionKey -> { + ClickType clickType = Enums.getIfPresent(ClickType.class, actionKey.toUpperCase()).orNull(); + + if (clickType != null) + { + ConfigurationSection actionDataSection = actionSection.getConfigurationSection(actionKey); + + if (actionDataSection != null) + { + ItemTemplateRecord.ActionRecords actionData = + new ItemTemplateRecord.ActionRecords(clickType, + actionDataSection.getString("type"), + actionDataSection.getString("content"), + actionDataSection.getString("tooltip")); + + itemRecord.addAction(actionData); + } + } + }); + } + } + + // Add item to the map + if (reusableItemMap != null && itemKey != null) + { + reusableItemMap.put(itemKey, itemRecord); + } + + return itemRecord; + } + + + /** + * This method clears loaded panels from the cache. + */ + public static void clearPanels() + { + loadedPanels.clear(); + } + + + /** + * This map contains already read panels and their location. + * This improves performance for GUI opening, with a some memory usage. + */ + private static final Map loadedPanels = new HashMap<>(); +} diff --git a/src/main/java/world/bentobox/bentobox/commands/BentoBoxReloadCommand.java b/src/main/java/world/bentobox/bentobox/commands/BentoBoxReloadCommand.java index 85ca4c7af..4ee6838c6 100644 --- a/src/main/java/world/bentobox/bentobox/commands/BentoBoxReloadCommand.java +++ b/src/main/java/world/bentobox/bentobox/commands/BentoBoxReloadCommand.java @@ -7,6 +7,7 @@ import org.bukkit.Bukkit; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.events.BentoBoxReadyEvent; +import world.bentobox.bentobox.api.panels.reader.TemplateReader; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.commands.reload.BentoBoxReloadLocalesCommand; import world.bentobox.bentobox.listeners.PanelListenerManager; @@ -45,6 +46,8 @@ public class BentoBoxReloadCommand extends ConfirmableCommand { // Close all open panels PanelListenerManager.closeAllPanels(); + // Clear all template panels. + TemplateReader.clearPanels(); // Reload settings getPlugin().loadSettings();