From 35ce1a7d81a45bb20448738f3823621b867f86ca Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 29 Sep 2022 18:44:07 +0300 Subject: [PATCH] Rework blueprint and blueprint bundle names. (#2026) * Fixes issue when blueprint clipboard was stuck after saving. The issue was that Map#putIfAbsent still creates a new task, even if object is already in map. The usage requires to use Map#computeIfAbsent. * Rework blueprint and blueprint bundle names. There was an issue with using non-english characters in blueprint names. It was not possible, as all chars for names were converted to lower cased english letters. It included display names. I reworked it a bit and now it should be possible to set non-english names for bundles and blueprints. Fixes #1954 --- .../blueprints/AdminBlueprintCommand.java | 30 ++-- .../AdminBlueprintDeleteCommand.java | 58 +++++--- .../blueprints/AdminBlueprintListCommand.java | 47 +++--- .../blueprints/AdminBlueprintLoadCommand.java | 2 +- .../AdminBlueprintRenameCommand.java | 105 +++++++++----- .../blueprints/AdminBlueprintSaveCommand.java | 121 +++++++++++----- .../commands/island/IslandCreateCommand.java | 6 +- .../commands/island/IslandResetCommand.java | 6 +- .../bentobox/blueprints/Blueprint.java | 4 +- .../blueprints/conversation/NamePrompt.java | 81 ++++++----- .../conversation/NameSuccessPrompt.java | 72 ++++++---- .../dataobjects/BlueprintBundle.java | 2 +- .../managers/BlueprintClipboardManager.java | 31 ++-- .../bentobox/managers/BlueprintsManager.java | 134 ++++++++++-------- .../world/bentobox/bentobox/util/Util.java | 21 ++- src/main/resources/locales/en-US.yml | 6 +- .../BlueprintClipboardManagerTest.java | 23 +-- .../managers/BlueprintsManagerTest.java | 7 +- 18 files changed, 479 insertions(+), 277 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java index a869ed72a..ca8a6eaf0 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java @@ -62,20 +62,30 @@ public class AdminBlueprintCommand extends ConfirmableCommand { return clipboards; } - protected void showClipboard(User user) { - displayClipboards.putIfAbsent(user, Bukkit.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> { - if (!user.isPlayer() || !user.getPlayer().isOnline()) { - hideClipboard(user); - } - if (clipboards.containsKey(user.getUniqueId())) { - BlueprintClipboard clipboard = clipboards.get(user.getUniqueId()); - paintAxis(user, clipboard); - } + /** + * This method shows clipboard for requested user. + * @param user User who need to see clipboard. + */ + protected void showClipboard(User user) + { + this.displayClipboards.computeIfAbsent(user, + key -> Bukkit.getScheduler().scheduleSyncRepeatingTask(this.getPlugin(), () -> + { + if (!key.isPlayer() || !key.getPlayer().isOnline()) + { + this.hideClipboard(key); + } - }, 20, 20)); + if (this.clipboards.containsKey(key.getUniqueId())) + { + BlueprintClipboard clipboard = this.clipboards.get(key.getUniqueId()); + this.paintAxis(key, clipboard); + } + }, 20, 20)); } + private void paintAxis(User user, BlueprintClipboard clipboard) { if (clipboard.getPos1() == null || clipboard.getPos2() == null) { return; diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java index 7b6de6df0..0a3615f77 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java @@ -8,49 +8,63 @@ import java.util.Optional; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.util.Util; + /** * Command that deletes a Blueprint. * @author Poslovitch * @since 1.9.0 */ -public class AdminBlueprintDeleteCommand extends ConfirmableCommand { - - public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent) { +public class AdminBlueprintDeleteCommand extends ConfirmableCommand +{ + public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent) + { super(parent, "delete", "remove"); } - @Override - public void setup() { - inheritPermission(); - setParametersHelp("commands.admin.blueprint.delete.parameters"); - setDescription("commands.admin.blueprint.delete.description"); - } @Override - public boolean execute(User user, String label, List args) { - if (args.size() != 1) { - showHelp(this, user); + public void setup() + { + this.inheritPermission(); + this.setParametersHelp("commands.admin.blueprint.delete.parameters"); + this.setDescription("commands.admin.blueprint.delete.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 1) + { + this.showHelp(this, user); return false; } - String blueprintName = args.get(0).toLowerCase(Locale.ENGLISH); + String blueprintName = Util.sanitizeInput(args.get(0)); // Check if blueprint exist - if (getPlugin().getBlueprintsManager().getBlueprints(getAddon()).containsKey(blueprintName)) { - askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"), () -> { - getPlugin().getBlueprintsManager().deleteBlueprint(getAddon(), blueprintName); - user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName); - }); + if (this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).containsKey(blueprintName)) + { + this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"), + () -> { + this.getPlugin().getBlueprintsManager().deleteBlueprint(this.getAddon(), blueprintName); + user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName); + }); return true; - } else { + } + else + { user.sendMessage("commands.admin.blueprint.delete.no-blueprint", TextVariables.NAME, blueprintName); return false; } } + @Override - public Optional> tabComplete(User user, String alias, List args) { - return Optional.of(new LinkedList<>(getPlugin().getBlueprintsManager().getBlueprints(getAddon()).keySet())); + public Optional> tabComplete(User user, String alias, List args) + { + return Optional.of(new LinkedList<>(this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).keySet())); } -} +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java index 4435871be..57f19325a 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java @@ -5,51 +5,66 @@ import java.io.FilenameFilter; import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.managers.BlueprintsManager; -public class AdminBlueprintListCommand extends CompositeCommand { +public class AdminBlueprintListCommand extends CompositeCommand +{ - public AdminBlueprintListCommand(AdminBlueprintCommand parent) { + public AdminBlueprintListCommand(AdminBlueprintCommand parent) + { super(parent, "list"); } @Override - public void setup() { - inheritPermission(); - setDescription("commands.admin.blueprint.list.description"); + public void setup() + { + this.inheritPermission(); + this.setDescription("commands.admin.blueprint.list.description"); } + @Override - public boolean canExecute(User user, String label, List args) { - if (!args.isEmpty()) { - showHelp(this, user); + public boolean canExecute(User user, String label, List args) + { + if (!args.isEmpty()) + { + this.showHelp(this, user); return false; } + return true; } @Override - public boolean execute(User user, String label, List args) { - File blueprints = new File(getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME); - if (!blueprints.exists()) { + public boolean execute(User user, String label, List args) + { + File blueprints = new File(this.getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME); + + if (!blueprints.exists()) + { user.sendMessage("commands.admin.blueprint.list.no-blueprints"); return false; } - FilenameFilter blueprintFilter = (File dir, String name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(BlueprintsManager.BLUEPRINT_SUFFIX); - List blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).collect(Collectors.toList()); - if (blueprintList.isEmpty()) { + + FilenameFilter blueprintFilter = (File dir, String name) -> name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX); + + List blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))). + map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())). + toList(); + + if (blueprintList.isEmpty()) + { user.sendMessage("commands.admin.blueprint.list.no-blueprints"); return false; } + user.sendMessage("commands.admin.blueprint.list.available-blueprints"); blueprintList.forEach(user::sendRawMessage); return true; } - } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java index 72a264816..6bbef1502 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java @@ -32,7 +32,7 @@ public class AdminBlueprintLoadCommand extends CompositeCommand { AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); BlueprintClipboardManager bp = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder()); - if (bp.load(user, args.get(0))) { + if (bp.load(user, Util.sanitizeInput(args.get(0)))) { parent.getClipboards().put(user.getUniqueId(), bp.getClipboard()); return true; } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java index b1a21a959..f7863715d 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java @@ -8,66 +8,107 @@ import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; +import world.bentobox.bentobox.blueprints.BlueprintClipboard; import world.bentobox.bentobox.managers.BlueprintsManager; +import world.bentobox.bentobox.util.Util; + /** * Renames an existing blueprint. * @author Poslovitch * @since 1.10.0 */ -public class AdminBlueprintRenameCommand extends ConfirmableCommand { - - public AdminBlueprintRenameCommand(AdminBlueprintCommand parent) { +public class AdminBlueprintRenameCommand extends ConfirmableCommand +{ + public AdminBlueprintRenameCommand(AdminBlueprintCommand parent) + { super(parent, "rename"); } - @Override - public void setup() { - inheritPermission(); - setParametersHelp("commands.admin.blueprint.rename.parameters"); - setDescription("commands.admin.blueprint.rename.description"); - } @Override - public boolean execute(User user, String label, List args) { - if (args.size() != 2) { - showHelp(this, user); + public void setup() + { + this.inheritPermission(); + this.setParametersHelp("commands.admin.blueprint.rename.parameters"); + this.setDescription("commands.admin.blueprint.rename.description"); + } + + + @Override + public boolean canExecute(User user, String label, List args) + { + if (args.size() != 2) + { + // Blueprint must have a name. + this.showHelp(this, user); return false; } - AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); + String from = Util.sanitizeInput(args.get(0)); + String to = Util.sanitizeInput(args.get(1)); - // Check if the names are the same - String from = args.get(0).toLowerCase(Locale.ENGLISH); - String to = args.get(1).toLowerCase(Locale.ENGLISH); - - if (from.equals(to)) { + // Check if name is changed. + if (from.equals(to)) + { user.sendMessage("commands.admin.blueprint.rename.pick-different-name"); return false; } // Check if the 'from' file exists + AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent(); File fromFile = new File(parent.getBlueprintsFolder(), from + BlueprintsManager.BLUEPRINT_SUFFIX); - if (!fromFile.exists()) { + + if (!fromFile.exists()) + { user.sendMessage("commands.admin.blueprint.no-such-file"); return false; } - // Check if the 'to' file exists - - File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX); - if (toFile.exists()) { - // Ask for confirmation to overwrite - askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> rename(user, from, to)); - } else { - askConfirmation(user, () -> rename(user, from, to)); - } return true; } - private void rename(User user, String blueprintName, String newName) { - Blueprint blueprint = getPlugin().getBlueprintsManager().getBlueprints(getAddon()).get(blueprintName); - getPlugin().getBlueprintsManager().renameBlueprint(getAddon(), blueprint, newName); - user.sendMessage("commands.admin.blueprint.rename.success", "[old]", blueprintName, TextVariables.NAME, blueprint.getName()); + + @Override + public boolean execute(User user, String label, List args) + { + AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); + + // Check if the names are the same + String from = Util.sanitizeInput(args.get(0)); + String to = Util.sanitizeInput(args.get(1)); + + // Check if the 'to' file exists + File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX); + + if (toFile.exists()) + { + // Ask for confirmation to overwrite + this.askConfirmation(user, + user.getTranslation("commands.admin.blueprint.file-exists"), + () -> this.rename(user, from, to, args.get(1))); + } + else + { + this.askConfirmation(user, () -> this.rename(user, from, to, args.get(1))); + } + + return true; + } + + + private void rename(User user, String blueprintName, String fileName, String displayName) + { + Blueprint blueprint = this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).get(blueprintName); + + this.getPlugin().getBlueprintsManager().renameBlueprint(this.getAddon(), blueprint, fileName, displayName); + + user.sendMessage("commands.admin.blueprint.rename.success", + "[old]", + blueprintName, + TextVariables.NAME, + blueprint.getName(), + "[display]", + blueprint.getDisplayName()); } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java index 7ec6af2ba..b0b957171 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java @@ -2,62 +2,115 @@ package world.bentobox.bentobox.api.commands.admin.blueprints; import java.io.File; import java.util.List; -import java.util.Locale; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.BlueprintClipboard; import world.bentobox.bentobox.managers.BlueprintClipboardManager; import world.bentobox.bentobox.managers.BlueprintsManager; +import world.bentobox.bentobox.util.Util; -public class AdminBlueprintSaveCommand extends ConfirmableCommand { - public AdminBlueprintSaveCommand(AdminBlueprintCommand parent) { +/** + * This method allows to save blueprint from the clipboard. + */ +public class AdminBlueprintSaveCommand extends ConfirmableCommand +{ + public AdminBlueprintSaveCommand(AdminBlueprintCommand parent) + { super(parent, "save"); } - @Override - public void setup() { - inheritPermission(); - setParametersHelp("commands.admin.blueprint.save.parameters"); - setDescription("commands.admin.blueprint.save.description"); - } @Override - public boolean execute(User user, String label, List args) { - if (args.size() != 1) { - showHelp(this, user); + public void setup() + { + this.inheritPermission(); + this.setParametersHelp("commands.admin.blueprint.save.parameters"); + this.setDescription("commands.admin.blueprint.save.description"); + } + + + @Override + public boolean canExecute(User user, String label, List args) + { + if (args.size() != 1) + { + // Blueprint must have a name. + this.showHelp(this, user); return false; } - AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); - BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); - String fileName = args.get(0).toLowerCase(Locale.ENGLISH); - if (clipboard.isFull()) { - // Check if blueprint had bedrock - if (clipboard.getBlueprint().getBedrock() == null) { - user.sendMessage("commands.admin.blueprint.bedrock-required"); - return false; - } - // Check if file exists - File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX); - if (newFile.exists()) { - this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> hideAndSave(user, parent, clipboard, fileName)); - return false; - } - return hideAndSave(user, parent, clipboard, fileName); - } else { + BlueprintClipboard clipboard = ((AdminBlueprintCommand) this.getParent()).getClipboards(). + computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); + + if (!clipboard.isFull()) + { + // Clipboard is not set up. user.sendMessage("commands.admin.blueprint.copy-first"); return false; } + + if (clipboard.getBlueprint().getBedrock() == null) + { + // Bedrock is required for all blueprints. + user.sendMessage("commands.admin.blueprint.bedrock-required"); + return false; + } + + return true; } - private boolean hideAndSave(User user, AdminBlueprintCommand parent, BlueprintClipboard clipboard, String name) { - parent.hideClipboard(user); - boolean result = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder(), clipboard).save(user, name); - if (result && clipboard.getBlueprint() != null) { - getPlugin().getBlueprintsManager().addBlueprint(getAddon(), clipboard.getBlueprint()); + + @Override + public boolean execute(User user, String label, List args) + { + AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent(); + BlueprintClipboard clipboard = parent.getClipboards(). + computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); + + String fileName = Util.sanitizeInput(args.get(0)); + + // Check if file exists + File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX); + + if (newFile.exists()) + { + this.askConfirmation(user, + user.getTranslation("commands.admin.blueprint.file-exists"), + () -> this.hideAndSave(user, parent, clipboard, fileName, args.get(0))); + return false; } + + return this.hideAndSave(user, parent, clipboard, fileName, args.get(0)); + } + + + /** + * This method saves given blueprint. + * @param user User that triggers blueprint save. + * @param parent Parent command that contains clipboard. + * @param clipboard Active clipboard. + * @param name Filename for the blueprint + * @param displayName Display name for the blueprint. + * @return {@code true} if blueprint is saved, {@code false} otherwise. + */ + private boolean hideAndSave(User user, + AdminBlueprintCommand parent, + BlueprintClipboard clipboard, + String name, + String displayName) + { + parent.hideClipboard(user); + boolean result = new BlueprintClipboardManager(this.getPlugin(), + parent.getBlueprintsFolder(), clipboard). + save(user, name, displayName); + + if (result && clipboard.isFull()) + { + this.getPlugin().getBlueprintsManager().addBlueprint(this.getAddon(), clipboard.getBlueprint()); + } + return result; } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java index 6e2d21d8b..5426b764b 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java @@ -12,6 +12,8 @@ import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.managers.island.NewIsland; import world.bentobox.bentobox.panels.IslandCreationPanel; +import world.bentobox.bentobox.util.Util; + /** * /island create - Create an island. @@ -63,13 +65,13 @@ public class IslandCreateCommand extends CompositeCommand { public boolean execute(User user, String label, List args) { // Permission check if the name is not the default one if (!args.isEmpty()) { - String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH)); + String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0))); if (name == null) { // The blueprint name is not valid. user.sendMessage("commands.island.create.unknown-blueprint"); return false; } - if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) { + if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) { return false; } // Make island diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java index 6e059af6b..f6c60aa83 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java @@ -17,6 +17,8 @@ import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.managers.island.NewIsland; import world.bentobox.bentobox.managers.island.NewIsland.Builder; import world.bentobox.bentobox.panels.IslandCreationPanel; +import world.bentobox.bentobox.util.Util; + /** * @author tastybento @@ -78,13 +80,13 @@ public class IslandResetCommand extends ConfirmableCommand { public boolean execute(User user, String label, List args) { // Permission check if the name is not the default one if (!args.isEmpty()) { - String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH)); + String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0))); if (name == null || name.isEmpty()) { // The blueprint name is not valid. user.sendMessage("commands.island.create.unknown-blueprint"); return false; } - if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) { + if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) { return false; } return resetIsland(user, name); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java b/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java index 38d73eec8..da3304b59 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java @@ -53,14 +53,14 @@ public class Blueprint { public String getName() { if (name == null) name = "unnamed"; // Force lower case - return name.toLowerCase(Locale.ENGLISH); + return name; } /** * @param name the name to set */ public Blueprint setName(@NonNull String name) { // Force lowercase - this.name = name.toLowerCase(Locale.ENGLISH); + this.name = name; return this; } /** diff --git a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java index 25b86506e..c74f288b7 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java @@ -1,7 +1,5 @@ package world.bentobox.bentobox.blueprints.conversation; -import java.util.Locale; - import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; import org.bukkit.conversations.StringPrompt; @@ -18,67 +16,74 @@ import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.util.Util; -public class NamePrompt extends StringPrompt { - +public class NamePrompt extends StringPrompt +{ private final GameModeAddon addon; + @Nullable private final BlueprintBundle bb; + @Nullable private Blueprint bp; - public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb) { + + public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb) + { this.addon = addon; this.bb = bb; } - public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb) { + + public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb) + { this.addon = addon; this.bp = bp; this.bb = bb; } + @Override - public @NonNull String getPromptText(ConversationContext context) { - User user = User.getInstance((Player)context.getForWhom()); + public @NonNull String getPromptText(ConversationContext context) + { + User user = User.getInstance((Player) context.getForWhom()); return user.getTranslation("commands.admin.blueprint.management.name.prompt"); } - @Override - public Prompt acceptInput(ConversationContext context, String input) { - User user = User.getInstance((Player)context.getForWhom()); - // Convert color codes - input = Util.translateColorCodes(input); - if (ChatColor.stripColor(input).length() > 32) { - context.getForWhom().sendRawMessage("Too long"); - return this; - /*Check if unique name contains chars not supported in regex expression - Cannot start, contain, or end with special char, cannot contain any numbers. - Can only contain - for word separation*/ - }else if (!ChatColor.stripColor(input).matches("^[a-zA-Z]+(?:-[a-zA-Z]+)*$")) { - context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.invalid-char-in-unique-name")); + @Override + public Prompt acceptInput(ConversationContext context, String input) + { + User user = User.getInstance((Player) context.getForWhom()); + String uniqueId = Util.sanitizeInput(input); + + // Convert color codes + if (ChatColor.stripColor(Util.translateColorCodes(input)).length() > 32) + { + context.getForWhom().sendRawMessage( + user.getTranslation("commands.admin.blueprint.management.name.too-long")); return this; } - if (bb == null || !bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) { - // Make a uniqueid - StringBuilder uniqueId = new StringBuilder(ChatColor.stripColor(input).toLowerCase(Locale.ENGLISH).replace(" ", "_")); + + if (this.bb == null || !this.bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) + { // Check if this name is unique - int max = 0; - while (max++ < 32 && addon.getPlugin().getBlueprintsManager().getBlueprintBundles(addon).containsKey(uniqueId.toString())) { - uniqueId.append("x"); - } - if (max == 32) { - context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name")); + if (this.addon.getPlugin().getBlueprintsManager().getBlueprintBundles(this.addon).containsKey(uniqueId)) + { + context.getForWhom().sendRawMessage( + user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name")); return this; } - context.setSessionData("uniqueId", uniqueId.toString()); - } else { - // Default stays as default - context.setSessionData("uniqueId", bb.getUniqueId()); + + context.setSessionData("uniqueId", uniqueId); } + else + { + // Default stays as default + context.setSessionData("uniqueId", this.bb.getUniqueId()); + } + context.setSessionData("name", input); - return new NameSuccessPrompt(addon, bb, bp); + + return new NameSuccessPrompt(this.addon, this.bb, this.bp); } - -} - +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java index 9e990fefe..2b2c6a4e3 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java @@ -15,58 +15,76 @@ import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle; import world.bentobox.bentobox.panels.BlueprintManagementPanel; -public class NameSuccessPrompt extends MessagePrompt { +public class NameSuccessPrompt extends MessagePrompt +{ private final GameModeAddon addon; + private BlueprintBundle bb; + private final Blueprint bp; + /** * Handles the name processing + * * @param addon - Game Mode addon * @param bb - Blueprint Bundle * @param bp - blueprint */ - public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp) { + public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp) + { this.addon = addon; this.bb = bb; this.bp = bp; } + @Override - public @NonNull String getPromptText(ConversationContext context) { + public @NonNull String getPromptText(ConversationContext context) + { String name = (String) context.getSessionData("name"); String uniqueId = (String) context.getSessionData("uniqueId"); - User user = User.getInstance((Player)context.getForWhom()); - // Rename blueprint - if (bp != null) { - BentoBox.getInstance().getBlueprintsManager().renameBlueprint(addon, bp, name); - new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openBB(bb); - } else { - // Blueprint Bundle - if (bb == null) { - // New Blueprint bundle - bb = new BlueprintBundle(); - bb.setIcon(Material.RED_WOOL); - } else { - // Rename - remove old named file - BentoBox.getInstance().getBlueprintsManager().deleteBlueprintBundle(addon, bb); - } - bb.setDisplayName(name); - bb.setUniqueId(uniqueId); - BentoBox.getInstance().getBlueprintsManager().addBlueprintBundle(addon, bb); - BentoBox.getInstance().getBlueprintsManager().saveBlueprintBundle(addon, bb); - new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openPanel(); - // Set the name - // if successfully + User user = User.getInstance((Player) context.getForWhom()); + + // Rename blueprint + if (this.bp != null) + { + this.addon.getPlugin().getBlueprintsManager().renameBlueprint(this.addon, this.bp, uniqueId, name); + new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openBB(this.bb); } + else + { + // Blueprint Bundle + if (this.bb == null) + { + // New Blueprint bundle + this.bb = new BlueprintBundle(); + this.bb.setIcon(Material.RED_WOOL); + } + else + { + // Rename - remove old named file + this.addon.getPlugin().getBlueprintsManager().deleteBlueprintBundle(this.addon, this.bb); + } + + this.bb.setDisplayName(name); + this.bb.setUniqueId(uniqueId); + this.addon.getPlugin().getBlueprintsManager().addBlueprintBundle(this.addon, this.bb); + this.addon.getPlugin().getBlueprintsManager().saveBlueprintBundle(this.addon, this.bb); + + new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openPanel(); + // Set the name + } + return user.getTranslation("commands.admin.blueprint.management.description.success"); } + @Override - protected Prompt getNextPrompt(@NonNull ConversationContext context) { + protected Prompt getNextPrompt(@NonNull ConversationContext context) + { return Prompt.END_OF_CONVERSATION; } - } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java index 19384d3fd..59f457c28 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java @@ -69,7 +69,7 @@ public class BlueprintBundle implements DataObject { */ @Override public String getUniqueId() { - return uniqueId.toLowerCase(Locale.ENGLISH); + return uniqueId; } /** * @param uniqueId the uniqueId to set diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java index 60e589a3c..8afb589fc 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java @@ -7,6 +7,7 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -86,24 +87,24 @@ public class BlueprintClipboardManager { /** * Loads a blueprint - * @param fileName - the filename without the suffix + * @param fileName - the sanitized filename without the suffix * @return the blueprint * @throws IOException exception if there's an issue loading or unzipping */ public Blueprint loadBlueprint(String fileName) throws IOException { - File zipFile = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX); + File zipFile = new File(blueprintFolder, fileName + BlueprintsManager.BLUEPRINT_SUFFIX); if (!zipFile.exists()) { plugin.logError(LOAD_ERROR + zipFile.getName()); throw new IOException(LOAD_ERROR + zipFile.getName()); } unzip(zipFile.getCanonicalPath()); - File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName)); + File file = new File(blueprintFolder, fileName); if (!file.exists()) { plugin.logError(LOAD_ERROR + file.getName()); throw new IOException(LOAD_ERROR + file.getName() + " temp file"); } Blueprint bp; - try (FileReader fr = new FileReader(file)) { + try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8)) { bp = gson.fromJson(fr, Blueprint.class); } catch (Exception e) { plugin.logError("Blueprint has JSON error: " + zipFile.getName()); @@ -114,7 +115,7 @@ public class BlueprintClipboardManager { if (bp.getBedrock() == null) { bp.setBedrock(new Vector(bp.getxSize() / 2, bp.getySize() / 2, bp.getzSize() / 2)); bp.getBlocks().put(bp.getBedrock(), new BlueprintBlock(Material.BEDROCK.createBlockData().getAsString())); - plugin.logWarning("Blueprint " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it."); + plugin.logWarning("Blueprint " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it."); } return bp; } @@ -130,7 +131,7 @@ public class BlueprintClipboardManager { load(fileName); } catch (IOException e1) { user.sendMessage("commands.admin.blueprint.could-not-load"); - plugin.logError("Could not load blueprint file: " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage()); + plugin.logError("Could not load blueprint file: " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage()); return false; } user.sendMessage("general.success"); @@ -143,14 +144,20 @@ public class BlueprintClipboardManager { * @param newName - new name of this blueprint * @return - true if successful, false if error */ - public boolean save(User user, String newName) { - if (clipboard.getBlueprint() != null) { - clipboard.getBlueprint().setName(newName); - if (saveBlueprint(clipboard.getBlueprint())) { + public boolean save(User user, String newName, String displayName) + { + if (this.clipboard.isFull()) + { + this.clipboard.getBlueprint().setName(newName); + this.clipboard.getBlueprint().setDisplayName(displayName); + + if (this.saveBlueprint(this.clipboard.getBlueprint())) + { user.sendMessage("general.success"); return true; } } + user.sendMessage("commands.admin.blueprint.could-not-save", "[message]", "Could not save temp blueprint file."); return false; } @@ -165,9 +172,9 @@ public class BlueprintClipboardManager { plugin.logError("Blueprint name was empty - could not save it"); return false; } - File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(blueprint.getName())); + File file = new File(blueprintFolder, blueprint.getName()); String toStore = gson.toJson(blueprint, Blueprint.class); - try (FileWriter fileWriter = new FileWriter(file)) { + try (FileWriter fileWriter = new FileWriter(file, StandardCharsets.UTF_8)) { fileWriter.write(toStore); } catch (IOException e) { plugin.logError("Could not save temporary blueprint file: " + file.getName()); diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java index 9ed648df3..ea06a903f 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java @@ -1,11 +1,9 @@ package world.bentobox.bentobox.managers; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; +import java.io.*; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -207,13 +205,24 @@ public class BlueprintsManager { bpf.mkdirs(); } boolean loaded = false; - File[] bundles = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_BUNDLE_SUFFIX)); + File[] bundles = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_BUNDLE_SUFFIX)); + if (bundles == null || bundles.length == 0) { return false; } + for (File file : bundles) { - try { - BlueprintBundle bb = gson.fromJson(new FileReader(file), BlueprintBundle.class); + + try (FileReader fileReader = new FileReader(file, StandardCharsets.UTF_8)) + { + if (!file.getName().equals(Util.sanitizeInput(file.getName()))) + { + // fail on all blueprints with incorrect names. + throw new InputMismatchException(file.getName()); + } + + BlueprintBundle bb = gson.fromJson(fileReader, BlueprintBundle.class); + if (bb != null) { // Make sure there is no existing bundle with the same uniqueId if (blueprintBundles.get(addon).stream().noneMatch(bundle -> bundle.getUniqueId().equals(bb.getUniqueId()))) { @@ -290,13 +299,17 @@ public class BlueprintsManager { plugin.logError("There is no blueprint folder for addon " + addon.getDescription().getName()); bpf.mkdirs(); } - File[] bps = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_SUFFIX)); + File[] bps = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_SUFFIX)); + if (bps == null || bps.length == 0) { plugin.logError("No blueprints found for " + addon.getDescription().getName()); return; } for (File file : bps) { - String fileName = file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length()); + + // Input sanitization is required for weirdos that edit files manually. + String fileName = Util.sanitizeInput(file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length())); + try { Blueprint bp = new BlueprintClipboardManager(plugin, bpf).loadBlueprint(fileName); bp.setName(fileName); @@ -345,9 +358,9 @@ public class BlueprintsManager { if (!bpf.exists()) { bpf.mkdirs(); } - File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX); + File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX); String toStore = gson.toJson(bb, BlueprintBundle.class); - try (FileWriter fileWriter = new FileWriter(fileName)) { + try (FileWriter fileWriter = new FileWriter(fileName, StandardCharsets.UTF_8)) { fileWriter.write(toStore); } catch (IOException e) { plugin.logError("Could not save blueprint bundle file: " + e.getMessage()); @@ -355,20 +368,6 @@ public class BlueprintsManager { }); } - /** - * Sanitizes a filename as much as possible retaining the original name - * @param name - filename to sanitize - * @return sanitized name - */ - public static String sanitizeFileName(String name) { - return name - .chars() - .mapToObj(i -> (char) i) - .map(c -> Character.isWhitespace(c) ? '_' : c) - .filter(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_') - .map(String::valueOf) - .collect(Collectors.joining()); - } /** * Saves all the blueprint bundles @@ -396,26 +395,35 @@ public class BlueprintsManager { * @param name name of the Blueprint to delete * @since 1.9.0 */ - public void deleteBlueprint(GameModeAddon addon, String name) { - List addonBlueprints = blueprints.get(addon); + public void deleteBlueprint(GameModeAddon addon, String name) + { + List addonBlueprints = this.blueprints.get(addon); Iterator it = addonBlueprints.iterator(); - while (it.hasNext()) { - Blueprint b = it.next(); - if (b.getName().equalsIgnoreCase(name)) { - it.remove(); - blueprints.put(addon, addonBlueprints); - File file = new File(getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX); + while (it.hasNext()) + { + Blueprint b = it.next(); + + if (b.getName().equalsIgnoreCase(name)) + { + it.remove(); + + File file = new File(this.getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX); + // Delete the file - try { + try + { Files.deleteIfExists(file.toPath()); - } catch (IOException e) { - plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage()); + } + catch (IOException e) + { + this.plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage()); } } } } + /** * Paste the islands to world * @@ -441,7 +449,7 @@ public class BlueprintsManager { plugin.logError("Tried to paste '" + name + "' but the bundle is not loaded!"); return false; } - BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase(Locale.ENGLISH)); + BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase()); if (!blueprints.containsKey(addon) || blueprints.get(addon).isEmpty()) { plugin.logError("No blueprints loaded for bundle '" + name + "'!"); return false; @@ -514,7 +522,7 @@ public class BlueprintsManager { if (name == null) { return null; } - if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name.toLowerCase(Locale.ENGLISH))) { + if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name)) { return name; } return null; @@ -546,7 +554,7 @@ public class BlueprintsManager { // Permission String permission = addon.getPermissionPrefix() + "island.create." + name; // Get Blueprint bundle - BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase(Locale.ENGLISH)); + BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase()); if (bb == null || (bb.isRequirePermission() && !name.equals(DEFAULT_BUNDLE_NAME) && !user.hasPermission(permission))) { user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, permission); return false; @@ -565,7 +573,7 @@ public class BlueprintsManager { blueprintBundles.get(addon).removeIf(k -> k.getUniqueId().equals(bb.getUniqueId())); } File bpf = getBlueprintsFolder(addon); - File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX); + File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX); try { Files.deleteIfExists(fileName.toPath()); } catch (IOException e) { @@ -579,25 +587,39 @@ public class BlueprintsManager { * @param addon - Game Mode Addon * @param bp - blueprint * @param name - new name + * @param displayName - display name for blueprint */ - public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name) { - if (bp.getName().equalsIgnoreCase(name)) { + public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name, String displayName) + { + if (bp.getName().equalsIgnoreCase(name)) + { // If the name is the same, do not do anything return; } - File bpf = getBlueprintsFolder(addon); - // Get the filename - File fileName = new File(bpf, sanitizeFileName(bp.getName()) + BLUEPRINT_SUFFIX); - // Delete the old file - try { - Files.deleteIfExists(fileName.toPath()); - } catch (IOException e) { - plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage()); - } - // Set new name - bp.setName(name.toLowerCase(Locale.ENGLISH)); - // Save it - saveBlueprint(addon, bp); - } + File bpf = this.getBlueprintsFolder(addon); + // Get the filename + File fileName = new File(bpf, bp.getName() + BLUEPRINT_SUFFIX); + // Delete the old file + + try + { + Files.deleteIfExists(fileName.toPath()); + } + catch (IOException e) + { + this.plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage()); + } + + // Remove blueprint from the blueprints. + this.blueprints.get(addon).remove(bp); + + // Set new name + bp.setName(name); + bp.setDisplayName(displayName); + + // Save it + this.saveBlueprint(addon, bp); + this.addBlueprint(addon, bp); + } } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index 6b4184aa0..ad56f9384 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -2,11 +2,7 @@ package world.bentobox.bentobox.util; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.Enumeration; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -761,4 +757,19 @@ public class Util { } return count; } + + + /** + * This method removes all special characters that are not allowed in filenames (windows). + * It also includes any white-spaces, as for some reason, I do like it more without them. + * Also, all cases are lower cased for easier blueprint mapping. + * @param input Input that need to be sanitized. + * @return A sanitized input without illegal characters in names. + */ + public static String sanitizeInput(String input) + { + return ChatColor.stripColor( + Util.translateColorCodes(input.replaceAll("[\\\\/:*?\"<>|\s]", "_"))). + toLowerCase(); + } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 1616bfe77..0dbc062cd 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -332,7 +332,7 @@ commands: rename: parameters: " " description: "rename a blueprint" - success: "&a Blueprint &b [old] &a has been successfully renamed to &b [name]&a." + success: "&a Blueprint &b [old] &a has been successfully renamed to &b [display]&a. Filename now is &b [name]&a." pick-different-name: "&c Please specify a name that is different from the blueprint's current name." management: back: "Back" @@ -366,9 +366,9 @@ commands: name: quit: "quit" prompt: "Enter a name, or 'quit' to quit" - too-long: "&c Too long" + too-long: "&c Too long name. Only 32 chars are allowed." pick-a-unique-name: "Please pick a more unique name" - invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " + stripped-char-in-unique-name: "&c Some chars were removed because they are not allowed. &a New ID will be &b [name]&a." success: "Success!" conversation-prefix: ">" description: diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java index 190daa323..4e0919ff8 100644 --- a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java @@ -39,6 +39,8 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.BlueprintClipboard; +import world.bentobox.bentobox.util.Util; + /** * @author tastybento @@ -345,7 +347,7 @@ public class BlueprintClipboardManagerTest { } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -361,14 +363,14 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "test1234")); + assertTrue(bcm.save(user, "test1234", "")); File bp = new File(blueprintFolder, "test1234.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -384,14 +386,14 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "test.1234/../../film")); - File bp = new File(blueprintFolder, "test1234film.blu"); + assertTrue(bcm.save(user, Util.sanitizeInput("test.1234/../../film"), "")); + File bp = new File(blueprintFolder, "test.1234_.._.._film.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -407,14 +409,14 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "日本語の言葉")); + assertTrue(bcm.save(user, "日本語の言葉", "")); File bp = new File(blueprintFolder, "日本語の言葉.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -430,8 +432,9 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "日本語の言葉/../../../config")); - File bp = new File(blueprintFolder, "日本語の言葉config.blu"); + + assertTrue(bcm.save(user, Util.sanitizeInput("日本語の言葉/../../../config"), "")); + File bp = new File(blueprintFolder, "日本語の言葉_.._.._.._config.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java index 12c79afed..63bb1ba26 100644 --- a/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java @@ -526,8 +526,6 @@ public class BlueprintsManagerTest { BlueprintsManager bpm = new BlueprintsManager(plugin); bpm.addBlueprintBundle(addon, bb); assertEquals("bundle", bpm.validate(addon, "bundle")); - // Mixed case - assertEquals("buNdle", bpm.validate(addon, "buNdle")); // Not there assertNull(bpm.validate(addon, "buNdle2")); } @@ -651,18 +649,19 @@ public class BlueprintsManagerTest { } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String, java.lang.String)}. */ @Test public void testRenameBlueprint() { // Save it BlueprintsManager bpm = new BlueprintsManager(plugin); bpm.saveBlueprint(addon, defaultBp); + bpm.addBlueprint(addon, defaultBp); File blueprints = new File(dataFolder, BlueprintsManager.FOLDER_NAME); File d = new File(blueprints, "bedrock.blu"); assertTrue(d.exists()); // Rename it - bpm.renameBlueprint(addon, defaultBp, "bedrock2"); + bpm.renameBlueprint(addon, defaultBp, "bedrock2", ""); assertFalse(d.exists()); d = new File(blueprints, "bedrock2.blu"); assertTrue(d.exists());