Compare commits

..
27 changed files with 1121 additions and 728 deletions

No files matched your search

@@ -174,16 +174,6 @@ public interface MVWorldManager {
*/
MultiverseWorld getMVWorld(String name);
/**
* Returns a {@link MultiverseWorld} if the world with name given exists, and null if it does not.
* This will search optionally for alias names.
*
* @param name The name or optionally the alias of the world to get.
* @param checkAliases Indicates whether to check for world alias name.
* @return A {@link MultiverseWorld} or null.
*/
MultiverseWorld getMVWorld(String name, boolean checkAliases);
/**
* Returns a {@link MultiverseWorld} if it exists, and null if it does not.
*
@@ -193,24 +183,13 @@ public interface MVWorldManager {
MultiverseWorld getMVWorld(World world);
/**
* Checks to see if the given name is a valid {@link MultiverseWorld}
* Searches based on world name AND alias.
* Checks to see if the given name is a valid {@link MultiverseWorld}.
*
* @param name The name or alias of the world to check.
* @return True if the world exists, false if not.
*/
boolean isMVWorld(String name);
/**
* Checks to see if the given name is a valid {@link MultiverseWorld}.
* Optionally searches by alias is specified.
*
* @param name The name or alias of the world to check.
* @param checkAliases Indicates whether to check for world alias name.
* @return True if the world exists, false if not.
*/
boolean isMVWorld(String name, boolean checkAliases);
/**
* Checks to see if the given world is a valid {@link MultiverseWorld}.
*
@@ -335,11 +314,4 @@ public interface MVWorldManager {
* does not exist. {@code includeLoaded} if the world exists and is loaded.
*/
boolean hasUnloadedWorld(String name, boolean includeLoaded);
/**
* Get all the possible worlds that Multiverse has detected to be importable.
*
* @return A collection of world names that are deemed importable.
*/
Collection<String> getPotentialWorlds();
}
@@ -11,7 +11,6 @@ import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVDestination;
import com.onarandombox.MultiverseCore.destination.InvalidDestination;
import com.onarandombox.MultiverseCore.utils.MVPermissions;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -38,7 +37,7 @@ public class CheckCommand extends MultiverseCommand {
@Override
public void runCommand(CommandSender sender, List<String> args) {
Player p = PlayerFinder.get(args.get(0), sender);
Player p = this.plugin.getServer().getPlayerExact(args.get(0));
if (p == null) {
sender.sendMessage("Could not find player " + ChatColor.GREEN + args.get(0));
sender.sendMessage("Are they online?");
@@ -8,6 +8,9 @@
package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.display.ColorAlternator;
import com.onarandombox.MultiverseCore.display.ContentDisplay;
import com.onarandombox.MultiverseCore.display.settings.MapDisplaySettings;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameRule;
@@ -16,7 +19,9 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionDefault;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Allows management of Anchor Destinations.
@@ -68,15 +73,23 @@ public class GamerulesCommand extends MultiverseCommand {
}
}
final StringBuilder gameRules = new StringBuilder();
for (final String gameRule : world.getGameRules()) {
if (gameRules.length() != 0) {
gameRules.append(ChatColor.WHITE).append(", ");
ContentDisplay.forContent(getGameRuleMap(world))
.header("=== Gamerules for %s%s%s ===", ChatColor.AQUA, world.getName(), ChatColor.WHITE)
.colorTool(ColorAlternator.with(ChatColor.GREEN, ChatColor.GOLD))
.setting(MapDisplaySettings.OPERATOR, ": ")
.show(sender);
}
private Map<String, Object> getGameRuleMap(World world) {
Map<String, Object> gameRuleMap = new HashMap<>();
for (GameRule<?> rule : GameRule.values()) {
Object value = world.getGameRuleValue(rule);
if (value == null) {
gameRuleMap.put(rule.getName(), "null");
continue;
}
gameRules.append(ChatColor.AQUA).append(gameRule).append(ChatColor.WHITE).append(": ");
gameRules.append(ChatColor.GREEN).append(world.getGameRuleValue(GameRule.getByName(gameRule)));
gameRuleMap.put(rule.getName(), value);
}
sender.sendMessage("=== Gamerules for " + ChatColor.AQUA + world.getName() + ChatColor.WHITE + " ===");
sender.sendMessage(gameRules.toString());
return gameRuleMap;
}
}
@@ -9,7 +9,7 @@ package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.utils.WorldNameChecker;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.pneumaticraft.commandhandler.CommandHandler;
import org.bukkit.ChatColor;
import org.bukkit.World.Environment;
@@ -18,6 +18,8 @@ import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -43,17 +45,55 @@ public class ImportCommand extends MultiverseCommand {
this.worldManager = this.plugin.getMVWorldManager();
}
private String getPotentialWorldStrings() {
final Collection<String> potentialWorlds = this.worldManager.getPotentialWorlds();
StringBuilder worldList = new StringBuilder();
ChatColor currColor = ChatColor.WHITE;
for (String world : potentialWorlds) {
worldList.append(currColor).append(world).append(' ');
currColor = currColor == ChatColor.WHITE ? ChatColor.YELLOW : ChatColor.WHITE;
/**
* A very basic check to see if a folder has a level.dat file.
* If it does, we can safely assume it's a world folder.
*
* @param worldFolder The File that may be a world.
* @return True if it looks like a world, false if not.
*/
private static boolean checkIfIsWorld(File worldFolder) {
if (worldFolder.isDirectory()) {
File[] files = worldFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.toLowerCase().endsWith(".dat");
}
});
if (files != null && files.length > 0) {
return true;
}
}
return false;
}
return worldList.toString();
private String getPotentialWorlds() {
File worldFolder = this.plugin.getServer().getWorldContainer();
if (worldFolder == null) {
return "";
}
File[] files = worldFolder.listFiles();
String worldList = "";
Collection<MultiverseWorld> worlds = this.worldManager.getMVWorlds();
List<String> worldStrings = new ArrayList<String>();
for (MultiverseWorld world : worlds) {
worldStrings.add(world.getName());
}
for (String world : this.worldManager.getUnloadedWorlds()) {
worldStrings.add(world);
}
ChatColor currColor = ChatColor.WHITE;
for (File file : files) {
if (file.isDirectory() && checkIfIsWorld(file) && !worldStrings.contains(file.getName())) {
worldList += currColor + file.getName() + " ";
if (currColor == ChatColor.WHITE) {
currColor = ChatColor.YELLOW;
} else {
currColor = ChatColor.WHITE;
}
}
}
return worldList;
}
private String trimWorldName(String userInput) {
@@ -66,7 +106,7 @@ public class ImportCommand extends MultiverseCommand {
String worldName = trimWorldName(args.get(0));
if (worldName.toLowerCase().equals("--list") || worldName.toLowerCase().equals("-l")) {
String worldList = this.getPotentialWorldStrings();
String worldList = this.getPotentialWorlds();
if (worldList.length() > 2) {
sender.sendMessage(ChatColor.AQUA + "====[ These look like worlds ]====");
sender.sendMessage(worldList);
@@ -109,10 +149,10 @@ public class ImportCommand extends MultiverseCommand {
if (!worldFile.exists()) {
sender.sendMessage(ChatColor.RED + "FAILED.");
String worldList = this.getPotentialWorldStrings();
String worldList = this.getPotentialWorlds();
sender.sendMessage("That world folder does not exist. These look like worlds to me:");
sender.sendMessage(worldList);
} else if (!WorldNameChecker.isValidWorldFolder(worldFile)) {
} else if (!checkIfIsWorld(worldFile)) {
sender.sendMessage(ChatColor.RED + "FAILED.");
sender.sendMessage(String.format("'%s' does not appear to be a world. It is lacking a .dat file.",
worldName));
@@ -9,122 +9,111 @@ package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.display.ColorAlternator;
import com.onarandombox.MultiverseCore.display.ContentDisplay;
import com.onarandombox.MultiverseCore.display.ContentFilter;
import com.onarandombox.MultiverseCore.display.DisplayHandlers;
import com.onarandombox.MultiverseCore.display.settings.PagedDisplaySettings;
import org.bukkit.ChatColor;
import org.bukkit.World.Environment;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* Displays a listing of all worlds that a player can enter.
*/
public class ListCommand extends PaginatedCoreCommand<String> {
public class ListCommand extends MultiverseCommand {
public ListCommand(MultiverseCore plugin) {
super(plugin);
this.setName("World Listing");
this.setCommandUsage("/mv list [page]");
this.setCommandUsage("/mv list [filter] [page]");
this.setArgRange(0, 2);
this.addKey("mvlist");
this.addKey("mvl");
this.addKey("mv list");
this.setPermission("multiverse.core.list.worlds", "Displays a listing of all worlds that you can enter.", PermissionDefault.OP);
this.setItemsPerPage(8); // SUPPRESS CHECKSTYLE: MagicNumberCheck
}
private List<String> getFancyWorldList(Player p) {
List<String> worldList = new ArrayList<String>();
for (MultiverseWorld world : this.plugin.getMVWorldManager().getMVWorlds()) {
if (p != null && (!this.plugin.getMVPerms().canEnterWorld(p, world))) {
continue;
}
ChatColor color = ChatColor.GOLD;
Environment env = world.getEnvironment();
if (env == Environment.NETHER) {
color = ChatColor.RED;
} else if (env == Environment.NORMAL) {
color = ChatColor.GREEN;
} else if (env == Environment.THE_END) {
color = ChatColor.AQUA;
}
StringBuilder builder = new StringBuilder();
builder.append(world.getColoredWorldString()).append(ChatColor.WHITE);
builder.append(" - ").append(color).append(world.getEnvironment());
if (world.isHidden()) {
if (p == null || this.plugin.getMVPerms().hasPermission(p, "multiverse.core.modify", true)) {
// Prefix hidden worlds with an "[H]"
worldList.add(ChatColor.GRAY + "[H]" + builder.toString());
}
} else {
worldList.add(builder.toString());
}
}
for (String name : this.plugin.getMVWorldManager().getUnloadedWorlds()) {
if (p == null || this.plugin.getMVPerms().hasPermission(p, "multiverse.access." + name, true)) {
worldList.add(ChatColor.GRAY + name + " - UNLOADED");
}
}
return worldList;
}
@Override
protected List<String> getFilteredItems(List<String> availableItems, String filter) {
List<String> filtered = new ArrayList<String>();
for (String s : availableItems) {
if (s.matches("(?i).*" + filter + ".*")) {
filtered.add(s);
}
}
return filtered;
}
@Override
protected String getItemText(String item) {
return item;
}
@Override
public void runCommand(CommandSender sender, List<String> args) {
sender.sendMessage(ChatColor.LIGHT_PURPLE + "====[ Multiverse World List ]====");
Player p = null;
if (sender instanceof Player) {
p = (Player) sender;
}
ContentFilter filter = ContentFilter.DEFAULT;
int page = 1;
FilterObject filterObject = this.getPageAndFilter(args);
List<String> availableWorlds = new ArrayList<String>(this.getFancyWorldList(p));
if (filterObject.getFilter().length() > 0) {
availableWorlds = this.getFilteredItems(availableWorlds, filterObject.getFilter());
if (availableWorlds.size() == 0) {
sender.sendMessage(ChatColor.RED + "Sorry... " + ChatColor.WHITE
+ "No worlds matched your filter: " + ChatColor.AQUA + filterObject.getFilter());
return;
// Either page or filter.
if (args.size() == 1) {
try {
page = Integer.parseInt(args.get(0));
} catch (NumberFormatException ignore) {
filter = new ContentFilter(args.get(0));
}
}
if (!(sender instanceof Player)) {
for (String c : availableWorlds) {
sender.sendMessage(c);
// Filter then page.
if (args.size() == 2) {
filter = new ContentFilter(args.get(0));
try {
page = Integer.parseInt(args.get(1));
} catch (NumberFormatException ignore) {
sender.sendMessage(ChatColor.RED + args.get(1) + " is not valid number!");
}
return;
}
int totalPages = (int) Math.ceil(availableWorlds.size() / (this.itemsPerPage + 0.0));
ContentDisplay.forContent(getListContents(sender))
.header("%s====[ Multiverse World List ]====", ChatColor.GOLD)
.displayHandler(DisplayHandlers.PAGE_LIST)
.colorTool(ColorAlternator.with(ChatColor.AQUA, ChatColor.GOLD))
.filter(filter)
.setting(PagedDisplaySettings.SHOW_PAGE, page)
.show(sender);
}
if (filterObject.getPage() > totalPages) {
filterObject.setPage(totalPages);
private Collection<String> getListContents(@NotNull CommandSender sender) {
Player player = (sender instanceof Player) ? (Player) sender : null;
List<String> worldList = this.plugin.getMVWorldManager().getMVWorlds().stream()
.filter(world -> player == null || plugin.getMVPerms().canEnterWorld(player, world))
.filter(world -> canSeeWorld(player, world))
.map(world -> hiddenText(world) + world.getColoredWorldString() + " - " + parseColouredEnvironment(world.getEnvironment()))
.collect(Collectors.toList());
this.plugin.getMVWorldManager().getUnloadedWorlds().stream()
.filter(world -> plugin.getMVPerms().hasPermission(sender, "multiverse.access." + world, true))
.map(world -> ChatColor.GRAY + world + " - UNLOADED")
.forEach(worldList::add);
return worldList;
}
private boolean canSeeWorld(Player player, MultiverseWorld world) {
return !world.isHidden()
|| player == null
|| this.plugin.getMVPerms().hasPermission(player, "multiverse.core.modify", true);
}
private String hiddenText(MultiverseWorld world) {
return (world.isHidden()) ? String.format("%s[H] ", ChatColor.GRAY) : "";
}
private String parseColouredEnvironment(World.Environment env) {
ChatColor color = ChatColor.GOLD;
switch (env) {
case NETHER:
color = ChatColor.RED;
break;
case NORMAL:
color = ChatColor.GREEN;
break;
case THE_END:
color = ChatColor.AQUA;
break;
}
sender.sendMessage(ChatColor.AQUA + " Page " + filterObject.getPage() + " of " + totalPages);
this.showPage(filterObject.getPage(), sender, availableWorlds);
return color + env.toString();
}
}
@@ -9,7 +9,6 @@ package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
@@ -51,7 +50,7 @@ public class SpawnCommand extends MultiverseCommand {
sender.sendMessage("You don't have permission to teleport another player to spawn. (multiverse.core.spawn.other)");
return;
}
Player target = PlayerFinder.get(args.get(0), sender);
Player target = this.plugin.getServer().getPlayerExact(args.get(0));
if (target != null) {
target.sendMessage("Teleporting to this world's spawn...");
spawnAccurately(target);
@@ -18,7 +18,6 @@ import com.onarandombox.MultiverseCore.destination.WorldDestination;
import com.onarandombox.MultiverseCore.enums.TeleportResult;
import com.onarandombox.MultiverseCore.event.MVTeleportEvent;
import com.onarandombox.MultiverseCore.api.SafeTTeleporter;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
@@ -29,6 +28,7 @@ import org.bukkit.permissions.PermissionDefault;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
/**
* Used to teleport players.
@@ -59,7 +59,7 @@ public class TeleportCommand extends MultiverseCommand {
String destinationName;
if (args.size() == 2) {
teleportee = PlayerFinder.get(args.get(0), sender);
teleportee = this.plugin.getServer().getPlayerExact(args.get(0));
if (teleportee == null) {
this.messaging.sendMessage(sender, String.format("Sorry, I couldn't find player: %s%s",
ChatColor.GOLD, args.get(0)), false);
@@ -75,9 +75,22 @@ public class TeleportCommand extends MultiverseCommand {
}
teleportee = (Player) sender;
}
// Special case for cannons:
if (destinationName.matches("(?i)cannon-[\\d]+(\\.[\\d]+)?")) {
String[] cannonSpeed = destinationName.split("-");
try {
double speed = Double.parseDouble(cannonSpeed[1]);
destinationName = "ca:" + teleportee.getWorld().getName() + ":" + teleportee.getLocation().getX()
+ "," + teleportee.getLocation().getY() + "," + teleportee.getLocation().getZ() + ":"
+ teleportee.getLocation().getPitch() + ":" + teleportee.getLocation().getYaw() + ":" + speed;
} catch (Exception e) {
destinationName = "i:invalid";
}
}
DestinationFactory df = this.plugin.getDestFactory();
MVDestination d = df.getPlayerAwareDestination(teleportee, destinationName);
MVDestination d = df.getDestination(destinationName);
MVTeleportEvent teleportEvent = new MVTeleportEvent(d, teleportee, teleporter, true);
this.plugin.getServer().getPluginManager().callEvent(teleportEvent);
@@ -11,27 +11,16 @@ import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVDestination;
import com.onarandombox.MultiverseCore.commands.TeleportCommand;
import com.onarandombox.MultiverseCore.utils.PermissionTools;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import com.pneumaticraft.commandhandler.Command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/** A factory class that will create destinations from specific strings. */
public class DestinationFactory {
private static final Pattern CANNON_PATTERN = Pattern.compile("(?i)cannon-[\\d]+(\\.[\\d]+)?");
private MultiverseCore plugin;
private Map<String, Class<? extends MVDestination>> destList;
private Command teleportCommand;
@@ -47,59 +36,6 @@ public class DestinationFactory {
}
}
/**
* Parse a destination that has relation to sender, such as a cannon or player destination.
*
* @param teleportee The player that is going to be teleported.
* @param destinationName The destination to parse.
* @return A non-null MVDestination
*/
@NotNull
public MVDestination getPlayerAwareDestination(@NotNull Player teleportee,
@NotNull String destinationName) {
// Prioritise world, in the event that a world is named after a player online.
if (Bukkit.getWorld(destinationName) != null) {
return getDestination(destinationName);
}
Player targetPlayer = PlayerFinder.get(destinationName, teleportee);
if (targetPlayer != null) {
return getDestination("pl:" + targetPlayer.getName());
}
if (CANNON_PATTERN.matcher(destinationName).matches()) {
return getDestination(parseCannonDest(teleportee, destinationName));
}
return getDestination(destinationName);
}
/**
* Parses a cannon destination.
*
* @param teleportee The player that is going to be teleported.
* @param destinationName The destination to parse.
* @return A destination string.
*/
@NotNull
private String parseCannonDest(@NotNull Player teleportee,
@NotNull String destinationName) {
String[] cannonSpeed = destinationName.split("-");
try {
double speed = Double.parseDouble(cannonSpeed[1]);
destinationName = "ca:" + teleportee.getWorld().getName() + ":" + teleportee.getLocation().getX()
+ "," + teleportee.getLocation().getY() + "," + teleportee.getLocation().getZ() + ":"
+ teleportee.getLocation().getPitch() + ":" + teleportee.getLocation().getYaw() + ":" + speed;
}
catch (Exception e) {
destinationName = "i:invalid";
}
return destinationName;
}
/**
* Gets a new destination from a string.
* Returns a new InvalidDestination if the string could not be parsed.
@@ -165,8 +101,4 @@ public class DestinationFactory {
this.teleportCommand.addAdditonalPermission(other);
return true;
}
public Collection<String> getRegisteredIdentifiers() {
return this.destList.keySet();
}
}
@@ -0,0 +1,62 @@
package com.onarandombox.MultiverseCore.display;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;
/**
* Helper class to switch between 2 {@link ChatColor}.
*/
public class ColorAlternator implements ColorTool {
/**
* Creates a new {@link ColorAlternator} with 2 {@link ChatColor}s.
*
* @param colorThis The first color.
* @param colorThat The second color.
* @return The {@link ColorAlternator} created for you.
*/
public static ColorAlternator with(@NotNull ChatColor colorThis,
@NotNull ChatColor colorThat) {
return new ColorAlternator(colorThis, colorThat);
}
private boolean switcher;
private final ChatColor thisColor;
private final ChatColor thatColor;
/**
* @param colorThis The first color.
* @param colorThat The second color.
*/
public ColorAlternator(@NotNull ChatColor colorThis,
@NotNull ChatColor colorThat) {
this.thisColor = colorThis;
this.thatColor = colorThat;
}
/**
* Gets the color. Everytime this method is called, it swaps the color that it returns.
*
* @return The color.
*/
@Override
public ChatColor get() {
return (this.switcher ^= true) ? this.thisColor : this.thatColor;
}
/**
* @return The first color.
*/
public ChatColor getThisColor() {
return thisColor;
}
/**
* @return The second color.
*/
public ChatColor getThatColor() {
return thatColor;
}
}
@@ -0,0 +1,22 @@
package com.onarandombox.MultiverseCore.display;
import org.bukkit.ChatColor;
/**
* Tools to allow customisation.
*/
@FunctionalInterface
public interface ColorTool {
/**
* Gets a chat color.
*
* @return The color.
*/
ChatColor get();
/**
* Default implementation of this interface. Returns a default white color.
*/
ColorTool DEFAULT = () -> ChatColor.WHITE;
}
@@ -0,0 +1,268 @@
package com.onarandombox.MultiverseCore.display;
import com.onarandombox.MultiverseCore.display.settings.DisplaySetting;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
/**
* Helps to display contents such as list and maps in a nicely formatted fashion.
*
* @param <T> Type of content to display.
*/
public class ContentDisplay<T> {
public static final String LINE_BREAK = "%br%";
/**
* Creates a ContentDisplay.Builder for the given content.
*
* @param content The content to be displayed.
* @param <T> The type of the content which can be inferred.
* @return A new Builder.
*/
public static <T> Builder<T> forContent(T content) {
return new Builder<>(content);
}
/**
* Creates a ContentDisplay.Builder for the given collection of content.
*
* @param content The content to be displayed.
* @return A new Builder.
*/
public static Builder<Collection<String>> forContent(Collection<String> content) {
return new Builder<>(content).displayHandler(DisplayHandlers.LIST);
}
/**
* Creates a ContentDisplay.Builder for the given map of content.
*
* @param content The content to be displayed.
* @return A new Builder.
*/
public static Builder<Map<String, Object>> forContent(Map<String, Object> content) {
return new Builder<>(content).displayHandler(DisplayHandlers.INLINE_MAP);
}
private final T contents;
private String header;
private String emptyMessage = "No matching content to display.";
private DisplayHandler<T> displayHandler;
private ColorTool colorTool = ColorTool.DEFAULT;
private ContentFilter filter = ContentFilter.DEFAULT;
private final Map<DisplaySetting<?>, Object> settingsMap = new WeakHashMap<>();
private ContentDisplay(T contents) {
this.contents = contents;
}
/**
* Do the actual displaying of contents to the sender.
*
* @param sender The CommandSender to show the display to.
*/
public void show(@NotNull CommandSender sender) {
Collection<String> formattedContent;
try {
formattedContent = (this.contents == null) ? null : this.displayHandler.format(sender, this);
} catch (DisplayFormatException e) {
sender.sendMessage(String.format("%sError: %s", ChatColor.RED, e.getMessage()));
return;
}
this.displayHandler.sendHeader(sender, this);
this.displayHandler.sendSubHeader(sender, this);
this.displayHandler.sendBody(sender, this, formattedContent);
}
/**
* @return Gets the header to display.
*/
public String getHeader() {
return header;
}
/**
* Sets the header text.
*/
public void setHeader(@NotNull String header) {
this.header = header;
}
/**
* @return Gets the contents to display.
*/
public T getContents() {
return contents;
}
/**
* @return Gets the message to display when no content is shown.
*/
@NotNull
public String getEmptyMessage() {
return emptyMessage;
}
/**
* @return Gets the display handler that formats and sends content to sender.
*/
@NotNull
public DisplayHandler<T> getDisplayHandler() {
return displayHandler;
}
/**
* @return Gets the color tool used.
*/
@NotNull
public ColorTool getColorTool() {
return colorTool;
}
/**
* @return Gets the filter used.
*/
@NotNull
public ContentFilter getFilter() {
return filter;
}
/**
* Gets the value for a given setting option.
*
* @param setting The setting option.
* @param <S> The setting type.
* @return Value set for the given setting.
*/
public <S> S getSetting(@NotNull DisplaySetting<S> setting) {
return (S) settingsMap.getOrDefault(setting, setting.defaultValue());
}
/**
* Sets other specific settings that may be used by the {@link DisplayHandler}.
*
* @param setting The settings option.
* @param value The value to set.
* @param <S> The type of setting.
*/
public <S> void setSetting(@NotNull DisplaySetting<S> setting, S value) {
this.settingsMap.put(setting, value);
}
/**
* Builds a {@link ContentDisplay}.
*
* @param <T> Type of content to display.
*/
public static class Builder<T> {
private final ContentDisplay<T> display;
private Builder(T content) {
this.display = new ContentDisplay<>(content);
}
/**
* Sets header to be displayed.
*
* @param header The header text.
* @param replacements String formatting replacements.
* @return The builder.
*/
@NotNull
public Builder<T> header(@NotNull String header, Object...replacements) {
this.display.header = String.format(header, replacements);
return this;
}
/**
* Sets the message to show when no content is available for display.
*
* @param emptyMessage The message text.
* @param replacements String formatting replacements.
* @return The builder.
*/
@NotNull
public Builder<T> emptyMessage(@NotNull String emptyMessage, Object...replacements) {
this.display.emptyMessage = String.format(emptyMessage, replacements);
return this;
}
/**
* Sets the display handler that does the formatting and sending of content. <b>Required.</b>
*
* @param displayHandler The display handler for the given content type.
* @return The builder.
*/
@NotNull
public Builder<T> displayHandler(@NotNull DisplayHandler<T> displayHandler) {
this.display.displayHandler = displayHandler;
return this;
}
/**
* Sets the color tool used to make messages more colourful.
*
* @param colorTool The color tool to use.
* @return The builder.
*/
@NotNull
public Builder<T> colorTool(@NotNull ColorTool colorTool) {
this.display.colorTool = colorTool;
return this;
}
/**
* Sets content filter used to match specific content to be displayed.
*
* @param filter The filter to use.
* @return The builder.
*/
@NotNull
public Builder<T> filter(@NotNull ContentFilter filter) {
this.display.filter = filter;
return this;
}
/**
* Sets other specific settings that may be used by the {@link DisplayHandler}.
*
* @param setting The settings option.
* @param value The value to set.
* @param <S> The type of setting.
* @return The builder.
*/
@NotNull
public <S> Builder<T> setting(@NotNull DisplaySetting<S> setting, S value) {
this.display.settingsMap.put(setting, value);
return this;
}
/**
* Validates and build the content display.
*
* @return The content display.
*/
@NotNull
public ContentDisplay<T> build() {
Objects.requireNonNull(this.display.displayHandler);
return this.display;
}
/**
* Build and show the content to the sender.
*
* @param sender The CommandSender to show the display to.
*/
public void show(CommandSender sender) {
this.build().show(sender);
}
}
}
@@ -0,0 +1,153 @@
package com.onarandombox.MultiverseCore.display;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* <p>Filter content and text based on regex matching.</p>
*
* <p>Compile regex pattern based on {@link ContentFilter#filterString}. When prefixed with 'r=',
* use {@link ContentFilter#filterString} as the full regex pattern. Else, set to any match that
* contains the {@link ContentFilter#filterString}.<p>
*/
public class ContentFilter {
public static final ContentFilter DEFAULT = new ContentFilter();
private static final Pattern REGEX_SPECIAL_CHARS = Pattern.compile("[.+*?\\[^\\]$(){}=!<>|:-\\\\]");
private String filterString;
private Pattern filterPattern;
private boolean exactMatch;
private ContentFilter() {
}
/**
* @param filterString The text to do matching, either plaintext or regex.
*/
public ContentFilter(@NotNull String filterString) {
this(filterString, false);
}
/**
* @param filterString The text to do matching, else plaintext or regex.
* @param exactMatch Should check for exact match when doing regex matching.
*/
public ContentFilter(@NotNull String filterString,
boolean exactMatch) {
this.filterString = filterString;
this.exactMatch = exactMatch;
parseFilter();
}
private void parseFilter() {
if (filterString == null) {
return;
}
if (filterString.startsWith("r=")) {
convertToMatcher(filterString.substring(2));
return;
}
String cleanedFilter = REGEX_SPECIAL_CHARS.matcher(filterString.toLowerCase()).replaceAll("\\\\$0");
convertToMatcher("(?i).*" + cleanedFilter + ".*");
}
/**
* Compile and store the regex into a {@link Pattern}.
*
* @param regex The regex text.
*/
private void convertToMatcher(@NotNull String regex) {
try {
this.filterPattern = Pattern.compile(regex);
Logging.finest("Parsed regex pattern: %s", this.filterPattern.toString());
}
catch (PatternSyntaxException ignored) {
Logging.warning("Error parsing regex: %s", filterString);
}
}
/**
* Do regex matching.
*
* @param text String to check regex on.
* @return True of matches regex pattern, false otherwise.
*/
public boolean checkMatch(@Nullable Object text) {
if (!hasFilter()) {
return true;
}
if (text == null || !hasValidPattern()) {
return false;
}
text = ChatColor.stripColor(String.valueOf(text));
return (exactMatch)
? filterPattern.matcher((CharSequence) text).matches()
: filterPattern.matcher((CharSequence) text).find();
}
/**
* Checks if a filter string is present.
*
* @return True if there is a filter string, else false.
*/
public boolean hasFilter() {
return filterString != null;
}
/**
* Checks if regex pattern syntax is valid.
*
* @return True if valid, else false.
*/
public boolean hasValidPattern() {
return filterPattern != null;
}
/**
* @return The filter string.
*/
@Nullable
public String getString() {
return filterString;
}
/**
* @return The regex pattern.
*/
@Nullable
public Pattern getPattern() {
return filterPattern;
}
/**
* @return True if filter is set to do exact matching, else false.
*/
public boolean isExactMatch() {
return exactMatch;
}
/**
* Nicely format the filter string to be used for showing the sender.
*
* @return The formatted filter string.
*/
public @NotNull String getFormattedString() {
return String.format("%sFilter: '%s'", ChatColor.ITALIC, filterString);
}
@Override
public String toString() {
return "ContentFilter{" +
"filterString='" + filterString + '\'' +
", filterPattern=" + filterPattern +
", exactMatch=" + exactMatch +
'}';
}
}
@@ -0,0 +1,25 @@
package com.onarandombox.MultiverseCore.display;
/**
* Thrown when an issue occur while formatting content.
*/
public class DisplayFormatException extends Exception {
public DisplayFormatException() {
}
public DisplayFormatException(String message) {
super(message);
}
public DisplayFormatException(String message, Throwable cause) {
super(message, cause);
}
public DisplayFormatException(Throwable cause) {
super(cause);
}
public DisplayFormatException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
@@ -0,0 +1,68 @@
package com.onarandombox.MultiverseCore.display;
import com.google.common.base.Strings;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Handles the formatting and sending of all content by the {@link ContentDisplay}.
*
* @param <T> Type of content to display.
*/
@FunctionalInterface
public interface DisplayHandler<T> {
/**
* Formats the raw content into a {@link Collection<String>} for displaying to the given sender.
*
* @param sender The {@link CommandSender} who will the content will be displayed to.
* @param display The responsible {@link ContentDisplay}.
* @return The formatted content.
* @throws DisplayFormatException Issue occurred while formatting content. E.g. invalid page.
*/
Collection<String> format(@NotNull CommandSender sender, @NotNull ContentDisplay<T> display)
throws DisplayFormatException;
/**
* Sends the header.
*
* @param sender The {@link CommandSender} who will the header will be displayed to.
* @param display The responsible {@link ContentDisplay}.
*/
default void sendHeader(@NotNull CommandSender sender, @NotNull ContentDisplay<T> display) {
if (!Strings.isNullOrEmpty(display.getHeader())) {
sender.sendMessage(display.getHeader());
}
}
/**
* Sends info such as filter and page.
*
* @param sender The {@link CommandSender} who will the sub header will be displayed to.
* @param display The responsible {@link ContentDisplay}.
*/
default void sendSubHeader(@NotNull CommandSender sender, @NotNull ContentDisplay<T> display) {
if (display.getFilter().hasFilter()) {
sender.sendMessage(String.format("%s[ %s ]", ChatColor.GRAY, display.getFilter().getFormattedString()));
}
}
/**
* Sends the content.
*
* @param sender The {@link CommandSender} who will the body will be displayed to.
* @param display The responsible {@link ContentDisplay}.
* @param formattedContent The content after being formatted by {@link #format(CommandSender, ContentDisplay)}
*/
default void sendBody(@NotNull CommandSender sender, @NotNull ContentDisplay<T> display,
Collection<String> formattedContent) {
if (formattedContent == null || formattedContent.size() == 0) {
sender.sendMessage(display.getEmptyMessage());
return;
}
sender.sendMessage(formattedContent.toArray(new String[0]));
}
}
@@ -0,0 +1,47 @@
package com.onarandombox.MultiverseCore.display;
import com.onarandombox.MultiverseCore.display.handlers.InlineListDisplayHandler;
import com.onarandombox.MultiverseCore.display.handlers.InlineMapDisplayHandler;
import com.onarandombox.MultiverseCore.display.handlers.ListDisplayHandler;
import com.onarandombox.MultiverseCore.display.handlers.PagedListDisplayHandler;
import com.onarandombox.MultiverseCore.display.settings.InlineDisplaySettings;
import com.onarandombox.MultiverseCore.display.settings.PagedDisplaySettings;
import com.onarandombox.MultiverseCore.display.settings.MapDisplaySettings;
import java.util.Collection;
import java.util.Map;
/**
* Various implementations of {@link DisplayHandler}.
*/
public class DisplayHandlers {
/**
* Standard list display.
*
* Supported settings: none.
*/
public static final DisplayHandler<Collection<String>> LIST = new ListDisplayHandler();
/**
* List display with paging.
*
* Supported settings: {@link PagedDisplaySettings#SHOW_PAGE}, {@link PagedDisplaySettings#LINES_PER_PAGE},
* {@link PagedDisplaySettings#PAGE_IN_CONSOLE}, {@link PagedDisplaySettings#DO_END_PADDING}.
*/
public static final DisplayHandler<Collection<String>> PAGE_LIST = new PagedListDisplayHandler();
/**
* Display a list inline.
*
* Supported settings: {@link InlineDisplaySettings#SEPARATOR}.
*/
public static final DisplayHandler<Collection<String>> INLINE_LIST = new InlineListDisplayHandler();
/**
* Display key value pair inline.
*
* Supported settings: {@link InlineDisplaySettings#SEPARATOR}, {@link MapDisplaySettings#OPERATOR}.
*/
public static final DisplayHandler<Map<String, Object>> INLINE_MAP = new InlineMapDisplayHandler();
}
@@ -0,0 +1,36 @@
package com.onarandombox.MultiverseCore.display.handlers;
import com.onarandombox.MultiverseCore.display.ContentDisplay;
import com.onarandombox.MultiverseCore.display.DisplayFormatException;
import com.onarandombox.MultiverseCore.display.DisplayHandler;
import com.onarandombox.MultiverseCore.display.settings.InlineDisplaySettings;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
public class InlineListDisplayHandler implements DisplayHandler<Collection<String>> {
@Override
public Collection<String> format(@NotNull CommandSender sender, @NotNull ContentDisplay<Collection<String>> display)
throws DisplayFormatException {
StringBuilder builder = new StringBuilder();
String separator = display.getSetting(InlineDisplaySettings.SEPARATOR);
for (Iterator<String> iterator = display.getContents().iterator(); iterator.hasNext(); ) {
String content = iterator.next();
if (!display.getFilter().checkMatch(content)) {
continue;
}
builder.append(display.getColorTool().get()).append(content);
if (iterator.hasNext()) {
builder.append(separator);
}
}
return (builder.length() == 0)
? Collections.singletonList(display.getEmptyMessage())
: Collections.singleton(builder.toString());
}
}
@@ -0,0 +1,44 @@
package com.onarandombox.MultiverseCore.display.handlers;
import com.onarandombox.MultiverseCore.display.ContentDisplay;
import com.onarandombox.MultiverseCore.display.DisplayFormatException;
import com.onarandombox.MultiverseCore.display.DisplayHandler;
import com.onarandombox.MultiverseCore.display.settings.InlineDisplaySettings;
import com.onarandombox.MultiverseCore.display.settings.MapDisplaySettings;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
public class InlineMapDisplayHandler implements DisplayHandler<Map<String, Object>> {
@Override
public Collection<String> format(@NotNull CommandSender sender,
@NotNull ContentDisplay<Map<String, Object>> display)
throws DisplayFormatException {
StringBuilder builder = new StringBuilder();
String separator = display.getSetting(InlineDisplaySettings.SEPARATOR);
String operator = display.getSetting(MapDisplaySettings.OPERATOR);
for (Iterator<Map.Entry<String, Object>> iterator = display.getContents().entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, Object> entry = iterator.next();
if (!display.getFilter().checkMatch(entry.getKey()) && !display.getFilter().checkMatch(entry.getValue())) {
continue;
}
builder.append(display.getColorTool().get())
.append(entry.getKey())
.append(operator)
.append(display.getColorTool().get())
.append(entry.getValue());
if (iterator.hasNext()) {
builder.append(separator);
}
}
return (builder.length() == 0)
? Collections.singletonList(display.getEmptyMessage())
: Collections.singleton(builder.toString());
}
}
@@ -0,0 +1,22 @@
package com.onarandombox.MultiverseCore.display.handlers;
import com.onarandombox.MultiverseCore.display.ContentDisplay;
import com.onarandombox.MultiverseCore.display.DisplayFormatException;
import com.onarandombox.MultiverseCore.display.DisplayHandler;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.stream.Collectors;
public class ListDisplayHandler implements DisplayHandler<Collection<String>> {
@Override
public Collection<String> format(@NotNull CommandSender sender, @NotNull ContentDisplay<Collection<String>> display)
throws DisplayFormatException {
return display.getContents().stream()
.filter(display.getFilter()::checkMatch)
.map(s -> (ContentDisplay.LINE_BREAK.equals(s)) ? "" : display.getColorTool().get() + s)
.collect(Collectors.toList());
}
}
@@ -0,0 +1,105 @@
package com.onarandombox.MultiverseCore.display.handlers;
import com.onarandombox.MultiverseCore.display.ContentDisplay;
import com.onarandombox.MultiverseCore.display.DisplayFormatException;
import com.onarandombox.MultiverseCore.display.settings.PagedDisplaySettings;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.IntStream;
public class PagedListDisplayHandler extends ListDisplayHandler {
@Override
public Collection<String> format(@NotNull CommandSender sender, @NotNull ContentDisplay<Collection<String>> display)
throws DisplayFormatException {
if (dontNeedPaging(sender, display)) {
return super.format(sender, display);
}
int pages = 1;
int currentLength = 0;
int targetPage = display.getSetting(PagedDisplaySettings.SHOW_PAGE);
int linesPerPage = display.getSetting(PagedDisplaySettings.LINES_PER_PAGE);
List<String> content = new ArrayList<>(linesPerPage);
// Calculate the paging.
for (String line : display.getContents()) {
if (!display.getFilter().checkMatch(line)) {
continue;
}
// When it's the next page.
boolean isLineBreak = ContentDisplay.LINE_BREAK.equals(line);
if (isLineBreak || ++currentLength > linesPerPage) {
pages++;
currentLength = 0;
if (isLineBreak) {
continue;
}
}
if (pages == targetPage) {
// Let first line be the header when no header is defined.
if (display.getHeader() == null) {
display.setHeader(line);
currentLength--;
continue;
}
content.add(display.getColorTool().get() + line);
}
}
// Page out of range.
if (targetPage < 1 || targetPage > pages) {
if (pages == 1) {
throw new DisplayFormatException("There is only 1 page!");
}
throw new DisplayFormatException("Please enter a page from 1 to " + pages + ".");
}
// No content
if (content.size() == 0) {
content.add(display.getEmptyMessage());
}
// Add empty lines to make output length consistent.
if (display.getSetting(PagedDisplaySettings.DO_END_PADDING)) {
IntStream.range(0, linesPerPage - content.size()).forEach(i -> content.add(""));
}
display.setSetting(PagedDisplaySettings.TOTAL_PAGE, pages);
return content;
}
@Override
public void sendSubHeader(@NotNull CommandSender sender, @NotNull ContentDisplay<Collection<String>> display) {
if (dontNeedPaging(sender, display)) {
super.sendSubHeader(sender, display);
return;
}
if (display.getFilter().hasFilter()) {
sender.sendMessage(String.format("%s[ Page %s of %s, %s ]",
ChatColor.GRAY,
display.getSetting(PagedDisplaySettings.SHOW_PAGE),
display.getSetting(PagedDisplaySettings.TOTAL_PAGE),
display.getFilter().getFormattedString())
);
return;
}
sender.sendMessage(String.format("%s[ Page %s of %s ]",
ChatColor.GRAY,
display.getSetting(PagedDisplaySettings.SHOW_PAGE),
display.getSetting(PagedDisplaySettings.TOTAL_PAGE))
);
}
private boolean dontNeedPaging(CommandSender sender, ContentDisplay<Collection<String>> display) {
return sender instanceof ConsoleCommandSender
&& !display.getSetting(PagedDisplaySettings.PAGE_IN_CONSOLE);
}
}
@@ -0,0 +1,19 @@
package com.onarandombox.MultiverseCore.display.settings;
import com.onarandombox.MultiverseCore.display.DisplayHandler;
/**
* Represents a setting option that can be used by {@link DisplayHandler}.
*
* @param <T>
*/
@FunctionalInterface
public interface DisplaySetting<T> {
/**
* Gets the default value of this Display Setting.
*
* @return The default value.
*/
T defaultValue();
}
@@ -0,0 +1,15 @@
package com.onarandombox.MultiverseCore.display.settings;
import com.onarandombox.MultiverseCore.display.DisplayHandler;
import org.bukkit.ChatColor;
/**
* Collection of {@link DisplaySetting} that are used by various {@link DisplayHandler}.
*/
public class InlineDisplaySettings {
/**
* Inline separator. E.g. '1, 2, 3'
*/
public static final DisplaySetting<String> SEPARATOR = () -> ChatColor.WHITE + ", ";
}
@@ -0,0 +1,15 @@
package com.onarandombox.MultiverseCore.display.settings;
import com.onarandombox.MultiverseCore.display.DisplayHandler;
import org.bukkit.ChatColor;
/**
* Collection of {@link DisplaySetting} that are used by various {@link DisplayHandler}.
*/
public class MapDisplaySettings {
/**
* The thing between a key value pair. E.g. 'Me = Smart'
*/
public static final DisplaySetting<String> OPERATOR = () -> ChatColor.WHITE + " = ";
}
@@ -0,0 +1,30 @@
package com.onarandombox.MultiverseCore.display.settings;
public class PagedDisplaySettings {
/**
* Page to display.
*/
public static final DisplaySetting<Integer> SHOW_PAGE = () -> 1;
/**
* Total pages available to display.
*/
public static final DisplaySetting<Integer> TOTAL_PAGE = () -> 1;
/**
* The max number of lines per page. This excludes header.
*/
public static final DisplaySetting<Integer> LINES_PER_PAGE = () -> 8;
/**
* Should add empty lines if content lines shown is less that {@link #LINES_PER_PAGE}.
*/
public static final DisplaySetting<Boolean> DO_END_PADDING = () -> true;
/**
* Should display with paging when it's sent to console.
*/
public static final DisplaySetting<Boolean> PAGE_IN_CONSOLE = () -> false;
}
@@ -1,176 +0,0 @@
package com.onarandombox.MultiverseCore.utils;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Helper class to get {@link Player} from name, UUID or Selectors.
*/
public class PlayerFinder {
private static final Pattern UUID_REGEX = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
private static final Pattern COMMA_SPLIT = Pattern.compile(",");
/**
* Get a {@link Player} based on an identifier of name UUID or selector.
*
* @param playerIdentifier An identifier of name UUID or selector.
* @param sender Target sender for selector.
* @return The player if found, else null.
*/
@Nullable
public static Player get(@NotNull String playerIdentifier,
@NotNull CommandSender sender) {
Player targetPlayer = getByName(playerIdentifier);
if (targetPlayer != null) {
return targetPlayer;
}
targetPlayer = getByUuid(playerIdentifier);
if (targetPlayer != null) {
return targetPlayer;
}
return getBySelector(playerIdentifier, sender);
}
/**
* Get multiple {@link Player} based on many identifiers of name UUID or selector.
*
* @param playerIdentifiers An identifier of multiple names, UUIDs or selectors, separated by comma.
* @param sender Target sender for selector.
* @return A list of all the {@link Player} found.
*/
@Nullable
public static List<Player> getMulti(@NotNull String playerIdentifiers,
@NotNull CommandSender sender) {
String[] playerIdentifierArray = COMMA_SPLIT.split(playerIdentifiers);
if (playerIdentifierArray == null || playerIdentifierArray.length == 0) {
return null;
}
List<Player> playerResults = new ArrayList<>();
for (String playerIdentifier : playerIdentifierArray) {
Player targetPlayer = getByName(playerIdentifier);
if (targetPlayer != null) {
playerResults.add(targetPlayer);
continue;
}
targetPlayer = getByUuid(playerIdentifier);
if (targetPlayer != null) {
playerResults.add(targetPlayer);
continue;
}
List<Player> targetPlayers = getMultiBySelector(playerIdentifier, sender);
if (targetPlayers != null) {
playerResults.addAll(targetPlayers);
}
}
return playerResults;
}
/**
* Get a {@link Player} based on player name.
*
* @param playerName Name of a {@link Player}.
* @return The player if found, else null.
*/
@Nullable
public static Player getByName(@NotNull String playerName) {
return Bukkit.getPlayerExact(playerName);
}
/**
* Get a {@link Player} based on player UUID.
*
* @param playerUuid UUID of a player.
* @return The player if found, else null.
*/
@Nullable
public static Player getByUuid(@NotNull String playerUuid) {
if (!UUID_REGEX.matcher(playerUuid).matches()) {
return null;
}
UUID uuid;
try {
uuid = UUID.fromString(playerUuid);
} catch (Exception e) {
return null;
}
return getByUuid(uuid);
}
/**
* Get a {@link Player} based on playerUUID.
*
* @param playerUuid UUID of a player.
* @return The player if found, else null.
*/
@Nullable
public static Player getByUuid(@NotNull UUID playerUuid) {
return Bukkit.getPlayer(playerUuid);
}
/**
* Get a {@link Player} based on vanilla selectors.
* https://minecraft.gamepedia.com/Commands#Target_selectors
*
* @param playerSelector A target selector, usually starts with an '@'.
* @param sender Target sender for selector.
* @return The player if only one found, else null.
*/
@Nullable
public static Player getBySelector(@NotNull String playerSelector,
@NotNull CommandSender sender) {
List<Player> matchedPlayers = getMultiBySelector(playerSelector, sender);
if (matchedPlayers == null || matchedPlayers.isEmpty()) {
Logging.warning("No player found with selector '%s' for %s.", playerSelector, sender.getName());
return null;
}
if (matchedPlayers.size() > 1) {
Logging.warning("Ambiguous selector result '%s' for %s (more than one player matched) - %s",
playerSelector, sender.getName(), matchedPlayers.toString());
return null;
}
return matchedPlayers.get(0);
}
/**
* Get multiple {@link Player} based on selector.
* https://minecraft.gamepedia.com/Commands#Target_selectors
*
* @param playerSelector A target selector, usually starts with an '@'.
* @param sender Target sender for selector.
* @return A list of all the {@link Player} found.
*/
@Nullable
public static List<Player> getMultiBySelector(@NotNull String playerSelector,
@NotNull CommandSender sender) {
if (playerSelector.charAt(0) != '@') {
return null;
}
try {
return Bukkit.selectEntities(sender, playerSelector).stream()
.filter(e -> e instanceof Player)
.map(e -> ((Player) e))
.collect(Collectors.toList());
} catch (IllegalArgumentException e) {
Logging.warning("An error occurred while parsing selector '%s' for %s. Is it is the correct format?",
playerSelector, sender.getName());
e.printStackTrace();
return null;
}
}
}
@@ -1,140 +0,0 @@
package com.onarandombox.MultiverseCore.utils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Utility class used to help in doing various reflection actions.
*/
public class ReflectHelper {
/**
* Try to get the {@link Class} based on its classpath.
*
* @param classPath The target classpath.
* @return A {@link Class} if found, else null.
*/
@Nullable
public static Class<?> getClass(String classPath) {
try {
return Class.forName(classPath);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Check if the {@link Class} for a give classpath is present/valid.
*
* @param classPath Target classpath.
* @return True if class path is a valid class, else false.
*/
public static boolean hasClass(String classPath) {
return getClass(classPath) != null;
}
/**
* Try to get a {@link Method} from a given class.
*
* @param clazz The class to search the method on.
* @param methodName Name of the method to get.
* @param parameterTypes Parameters present for that method.
* @param <C> The class type.
* @return A {@link Method} if found, else null.
*/
@Nullable
public static <C> Method getMethod(Class<C> clazz, String methodName, Class<?>... parameterTypes) {
try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Try to get a {@link Method} from a given class.
*
* @param classInstance Instance of the class to search the method on.
* @param methodName Name of the method to get.
* @param parameterTypes Parameters present for that method.
* @param <C> The class type.
* @return A {@link Method} if found, else null.
*/
@Nullable
public static <C> Method getMethod(C classInstance, String methodName, Class<?>... parameterTypes) {
return getMethod(classInstance.getClass(), methodName, parameterTypes);
}
/**
* Calls a {@link Method}.
*
* @param classInstance Instance of the class responsible for the method.
* @param method The method to call.
* @param parameters Parameters needed when calling the method.
* @param <C> The class type.
* @param <R> The return type.
* @return Return value of the method call if any, else null.
*/
@Nullable
public static <C, R> R invokeMethod(C classInstance, Method method, Object...parameters) {
try {
return (R) method.invoke(classInstance, parameters);
} catch (Exception e) {
return null;
}
}
/**
* Try to get a {@link Field} from a given class.
*
* @param clazz The class to search the field on.
* @param fieldName Name of the field to get.
* @param <C> The class type.
* @return A {@link Field} if found, else null.
*/
@Nullable
public static <C> Field getField(Class<C> clazz, String fieldName) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
return null;
}
}
/**
* Try to get a {@link Field} from a given class.
*
* @param classInstance Instance of the class to search the field on.
* @param fieldName Name of the field to get.
* @param <C> The class type.
* @return A {@link Field} if found, else null.
*/
@Nullable
public static <C> Field getField(C classInstance, String fieldName) {
return getField(classInstance.getClass(), fieldName);
}
/**
* Gets the value of an {@link Field} from an instance of the class responsible.
*
* @param classInstance Instance of the class to get the field value from.
* @param field The field to get value from.
* @param <C> The class type.
* @param <V> The field value type.
* @return The field value if any, else null.
*/
@Nullable
public static <C, V> V getFieldValue(C classInstance, Field field) {
try {
return (V) field.get(classInstance);
} catch (IllegalAccessException e) {
return null;
}
}
}
@@ -36,19 +36,19 @@ import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Stack;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Public facing API to add/remove Multiverse worlds.
@@ -130,7 +130,7 @@ public class WorldManager implements MVWorldManager {
}
// Check for valid world name
if (!(WorldNameChecker.isValidWorldName(oldName) && WorldNameChecker.isValidWorldName(oldName))) {
if (!(isValidWorldName(oldName) && isValidWorldName(newName))) {
return false;
}
@@ -238,9 +238,7 @@ public class WorldManager implements MVWorldManager {
return false;
}
if (!WorldNameChecker.isValidWorldName(name)) {
Logging.warning("Invalid world name '" + name + "'");
Logging.warning("World name should not contain spaces or special characters!");
if (!isValidWorldName(name)) {
return false;
}
@@ -426,6 +424,21 @@ public class WorldManager implements MVWorldManager {
}
}
/**
* Check if the world name is allowed
*
* @param name Name of the world
* @return True if the world world name is valid based on regex
*/
private boolean isValidWorldName(String name) {
if (!worldNamePattern.matcher(name).matches()) {
Logging.warning("Invalid world name '" + name + "'");
Logging.warning("World name should not contain spaces or special characters!");
return false;
}
return true;
}
private void brokenWorld(String name) {
Logging.severe("The world '" + name + "' could NOT be loaded because it contains errors and is probably corrupt!");
Logging.severe("Try using Minecraft Region Fixer to repair your world! '" + name + "'");
@@ -617,14 +630,6 @@ public class WorldManager implements MVWorldManager {
*/
@Override
public MultiverseWorld getMVWorld(String name) {
return this.getMVWorld(name, true);
}
/**
* {@inheritDoc}
*/
@Override
public MultiverseWorld getMVWorld(String name, boolean checkAliases) {
if (name == null) {
return null;
}
@@ -632,7 +637,7 @@ public class WorldManager implements MVWorldManager {
if (world != null) {
return world;
}
return (checkAliases) ? this.getMVWorldByAlias(name) : null;
return this.getMVWorldByAlias(name);
}
/**
@@ -641,7 +646,7 @@ public class WorldManager implements MVWorldManager {
@Override
public MultiverseWorld getMVWorld(World world) {
if (world != null) {
return this.getMVWorld(world.getName(), false);
return this.getMVWorld(world.getName());
}
return null;
}
@@ -666,15 +671,7 @@ public class WorldManager implements MVWorldManager {
*/
@Override
public boolean isMVWorld(final String name) {
return this.isMVWorld(name, true);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMVWorld(final String name, boolean checkAliases) {
return this.worlds.containsKey(name) || (checkAliases && this.isMVWorldAlias(name));
return (this.worlds.containsKey(name) || isMVWorldAlias(name));
}
/**
@@ -956,21 +953,4 @@ public class WorldManager implements MVWorldManager {
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getPotentialWorlds() {
File worldContainer = this.plugin.getServer().getWorldContainer();
if (worldContainer == null) {
return Collections.emptyList();
}
return Arrays.stream(worldContainer.listFiles())
.filter(File::isDirectory)
.filter(folder -> !this.isMVWorld(folder.getName(), false))
.filter(WorldNameChecker::isValidWorldFolder)
.map(File::getName)
.collect(Collectors.toList());
}
}
@@ -1,159 +0,0 @@
package com.onarandombox.MultiverseCore.utils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
/**
* <p>Utility class in helping to check the status of a world name and it's associated world folder.</p>
*
* <p>Note this is for preliminary checks and better command output. A valid result will suggest but not
* 100% determine that a world name can be created, loaded or imported.</p>
*/
public class WorldNameChecker {
private static final Pattern WORLD_NAME_PATTERN = Pattern.compile("[a-zA-Z0-9/._-]+");
private static final Set<String> BLACKLIST_NAMES = Collections.unmodifiableSet(new HashSet<String>() {{
add("plugins");
add("logs");
add("cache");
add("crash-reports");
}});
/**
* Checks if a world name is valid.
*
* @param worldName The world name to check on.
* @return True if check result is valid, else false.
*/
public static boolean isValidWorldName(@Nullable String worldName) {
return checkName(worldName) == NameStatus.VALID;
}
/**
* Checks the current validity status of a world name.
*
* @param worldName The world name to check on.
* @return The resulting name status.
*/
@NotNull
public static NameStatus checkName(@Nullable String worldName) {
if (BLACKLIST_NAMES.contains(worldName)) {
return NameStatus.BLACKLISTED;
}
if (worldName == null || !WORLD_NAME_PATTERN.matcher(worldName).matches()) {
return NameStatus.INVALID_CHARS;
}
return NameStatus.VALID;
}
/**
* Checks if a world name has a valid world folder.
*
* @param worldName The world name to check on.
* @return True if check result is valid, else false.
*/
public static boolean isValidWorldFolder(@Nullable String worldName) {
return checkFolder(worldName) == FolderStatus.VALID;
}
/**
* Checks if a world folder is valid.
*
* @param worldFolder The world folder to check on.
* @return True if check result is valid, else false.
*/
public static boolean isValidWorldFolder(@Nullable File worldFolder) {
return checkFolder(worldFolder) == FolderStatus.VALID;
}
/**
* Checks the current folder status for a world name.
*
* @param worldName The world name to check on.
* @return The resulting folder status.
*/
@NotNull
public static FolderStatus checkFolder(@Nullable String worldName) {
if (worldName == null) {
return FolderStatus.DOES_NOT_EXIST;
}
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
return checkFolder(worldFolder);
}
/**
* Checks the current folder status.
*
* @param worldFolder The world folder to check on.
* @return The resulting folder status.
*/
@NotNull
public static FolderStatus checkFolder(@Nullable File worldFolder) {
if (worldFolder == null || !worldFolder.exists() || !worldFolder.isDirectory()) {
return FolderStatus.DOES_NOT_EXIST;
}
if (!folderHasDat(worldFolder)) {
return FolderStatus.NOT_A_WORLD;
}
return FolderStatus.VALID;
}
/**
* A very basic check to see if a folder has a level.dat file. If it does, we can safely assume
* it's a world folder.
*
* @param worldFolder The File that may be a world.
* @return True if it looks like a world, else false.
*/
private static boolean folderHasDat(@NotNull File worldFolder) {
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase().endsWith(".dat"));
return files != null && files.length > 0;
}
/**
* Result after checking validity of world name.
*/
public enum NameStatus {
/**
* Name is valid.
*/
VALID,
/**
* Name not valid as it contains invalid characters.
*/
INVALID_CHARS,
/**
* Name not valid as it is deemed blacklisted.
*/
BLACKLISTED
}
/**
* Result after checking validity of world folder.
*/
public enum FolderStatus {
/**
* Folder is valid.
*/
VALID,
/**
* Folder exist, but contents in it doesnt look like a world.
*/
NOT_A_WORLD,
/**
* Folder does not exist.
*/
DOES_NOT_EXIST
}
}