Implement new content display system.

This commit is contained in:
benwoo1110 2021-03-07 17:35:19 +08:00
parent 7c59dcbcb9
commit 5c51cb9ff9
11 changed files with 930 additions and 92 deletions

View File

@ -8,6 +8,11 @@
package com.onarandombox.MultiverseCore.commands; package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.displaytools.ColorAlternator;
import com.onarandombox.MultiverseCore.displaytools.ContentDisplay;
import com.onarandombox.MultiverseCore.displaytools.DisplayHandlers;
import com.onarandombox.MultiverseCore.displaytools.DisplaySettings;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.GameRule; import org.bukkit.GameRule;
@ -16,7 +21,10 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionDefault; import org.bukkit.permissions.PermissionDefault;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Allows management of Anchor Destinations. * Allows management of Anchor Destinations.
@ -68,15 +76,26 @@ public class GamerulesCommand extends MultiverseCommand {
} }
} }
final StringBuilder gameRules = new StringBuilder(); new ContentDisplay.Builder<Map<String, Object>>()
for (final String gameRule : world.getGameRules()) { .sender(sender)
if (gameRules.length() != 0) { .header("=== Gamerules for %s%s%s ===", ChatColor.AQUA, world.getName(), ChatColor.WHITE)
gameRules.append(ChatColor.WHITE).append(", "); .contents(getGameRuleMap(world))
.displayHandler(DisplayHandlers.INLINE_MAP)
.colorTool(ColorAlternator.with(ChatColor.GREEN, ChatColor.GOLD))
.setting(DisplaySettings.OPERATOR, ": ")
.display();
} }
gameRules.append(ChatColor.AQUA).append(gameRule).append(ChatColor.WHITE).append(": ");
gameRules.append(ChatColor.GREEN).append(world.getGameRuleValue(GameRule.getByName(gameRule))); 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;
} }
sender.sendMessage("=== Gamerules for " + ChatColor.AQUA + world.getName() + ChatColor.WHITE + " ==="); gameRuleMap.put(rule.getName(), value);
sender.sendMessage(gameRules.toString()); }
return gameRuleMap;
} }
} }

View File

@ -7,124 +7,118 @@
package com.onarandombox.MultiverseCore.commands; package com.onarandombox.MultiverseCore.commands;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MultiverseWorld; import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.displaytools.ColorAlternator;
import com.onarandombox.MultiverseCore.displaytools.ContentDisplay;
import com.onarandombox.MultiverseCore.displaytools.ContentFilter;
import com.onarandombox.MultiverseCore.displaytools.DisplayHandlers;
import com.onarandombox.MultiverseCore.displaytools.DisplaySettings;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.World.Environment; import org.bukkit.World;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionDefault; import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* Displays a listing of all worlds that a player can enter. * 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) { public ListCommand(MultiverseCore plugin) {
super(plugin); super(plugin);
this.setName("World Listing"); this.setName("World Listing");
this.setCommandUsage("/mv list [page]"); this.setCommandUsage("/mv list [filter] [page]");
this.setArgRange(0, 2); this.setArgRange(0, 2);
this.addKey("mvlist"); this.addKey("mvlist");
this.addKey("mvl"); this.addKey("mvl");
this.addKey("mv list"); this.addKey("mv list");
this.setPermission("multiverse.core.list.worlds", "Displays a listing of all worlds that you can enter.", PermissionDefault.OP); 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 @Override
public void runCommand(CommandSender sender, List<String> args) { public void runCommand(CommandSender sender, List<String> args) {
sender.sendMessage(ChatColor.LIGHT_PURPLE + "====[ Multiverse World List ]===="); ContentFilter filter = ContentFilter.DEFAULT;
Player p = null; int page = 1;
if (sender instanceof Player) {
p = (Player) sender;
}
// Either page or filter.
FilterObject filterObject = this.getPageAndFilter(args); if (args.size() == 1) {
try {
List<String> availableWorlds = new ArrayList<String>(this.getFancyWorldList(p)); page = Integer.parseInt(args.get(0));
if (filterObject.getFilter().length() > 0) { } catch (NumberFormatException ignore) {
availableWorlds = this.getFilteredItems(availableWorlds, filterObject.getFilter()); filter = new ContentFilter(args.get(0));
if (availableWorlds.size() == 0) {
sender.sendMessage(ChatColor.RED + "Sorry... " + ChatColor.WHITE
+ "No worlds matched your filter: " + ChatColor.AQUA + filterObject.getFilter());
return;
} }
} }
if (!(sender instanceof Player)) { // Filter then page.
for (String c : availableWorlds) { if (args.size() == 2) {
sender.sendMessage(c); 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)); new ContentDisplay.Builder<Collection<String>>()
.sender(sender)
if (filterObject.getPage() > totalPages) { .header("%s====[ Multiverse World List ]====", ChatColor.GOLD)
filterObject.setPage(totalPages); .contents(getListContents(sender))
.displayHandler(DisplayHandlers.PAGE_LIST)
.colorTool(ColorAlternator.with(ChatColor.AQUA, ChatColor.GOLD))
.filter(filter)
.setting(DisplaySettings.SHOW_PAGE, page)
.display();
} }
sender.sendMessage(ChatColor.AQUA + " Page " + filterObject.getPage() + " of " + totalPages); private List<String> getListContents(@NotNull CommandSender sender) {
Player player = (sender instanceof Player) ? (Player) sender : null;
this.showPage(filterObject.getPage(), sender, availableWorlds); 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;
}
return color + env.toString();
} }
} }

View File

@ -0,0 +1,62 @@
package com.onarandombox.MultiverseCore.displaytools;
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;
}
}

View File

@ -0,0 +1,21 @@
package com.onarandombox.MultiverseCore.displaytools;
import org.bukkit.ChatColor;
/**
* Tools to allow customisation.
*/
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;
}

View File

@ -0,0 +1,264 @@
package com.onarandombox.MultiverseCore.displaytools;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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%";
private CommandSender sender;
private String header;
private T contents;
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() { }
/**
* Do the actual displaying of contents to the sender.
*/
public void send() {
Collection<String> formattedContent;
try {
formattedContent = (this.contents == null) ? null : this.displayHandler.format(this);
} catch (DisplayFormatException e) {
this.sender.sendMessage(String.format("%sError: %s", ChatColor.RED, e.getMessage()));
return;
}
this.displayHandler.sendHeader(this);
this.displayHandler.sendSubHeader(this);
this.displayHandler.sendBody(this, formattedContent);
}
/**
* @return Gets the target sender.
*/
@NotNull
public CommandSender getSender() {
return sender;
}
/**
* @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;
public Builder() {
this.display = new ContentDisplay<>();
}
/**
* Sets target sender to display message to. <b>Required.</b>
*
* @param sender The target sender.
* @return The builder.
*/
@NotNull
public Builder<T> sender(@NotNull CommandSender sender) {
this.display.sender = sender;
return this;
}
/**
* 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 content to be displayed.
*
* @param contents The contents.
* @return The builder.
*/
@NotNull
public Builder<T> contents(@Nullable T contents) {
this.display.contents = contents;
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.sender);
Objects.requireNonNull(this.display.displayHandler);
return this.display;
}
/**
* Build and send the contents to the sender.
*/
public void display() {
this.build().send();
}
}
}

View File

@ -0,0 +1,153 @@
package com.onarandombox.MultiverseCore.displaytools;
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 +
'}';
}
}

View File

@ -0,0 +1,25 @@
package com.onarandombox.MultiverseCore.displaytools;
/**
* 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);
}
}

View File

@ -0,0 +1,61 @@
package com.onarandombox.MultiverseCore.displaytools;
import com.google.common.base.Strings;
import org.bukkit.ChatColor;
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.
*/
public interface DisplayHandler<T> {
/**
* Formats the raw content into a {@link Collection<String>} for displaying to sender.
*
* @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 ContentDisplay<T> display) throws DisplayFormatException;
/**
* Sends the header.
*
* @param display The responsible {@link ContentDisplay}.
*/
default void sendHeader(@NotNull ContentDisplay<T> display) {
if (!Strings.isNullOrEmpty(display.getHeader())) {
display.getSender().sendMessage(display.getHeader());
}
}
/**
* Sends info such as filter and page.
*
* @param display The responsible {@link ContentDisplay}.
*/
default void sendSubHeader(@NotNull ContentDisplay<T> display) {
if (display.getFilter().hasFilter()) {
display.getSender().sendMessage(String.format("%s[ %s ]",
ChatColor.GRAY, display.getFilter().getFormattedString()));
}
}
/**
* Sends the content.
*
* @param display The responsible {@link ContentDisplay}.
* @param formattedContent The content after being formatted by {@link #format(ContentDisplay)}
*/
default void sendBody(@NotNull ContentDisplay<T> display, Collection<String> formattedContent) {
if (formattedContent == null || formattedContent.size() == 0) {
display.getSender().sendMessage(display.getEmptyMessage());
return;
}
display.getSender().sendMessage(formattedContent.toArray(new String[0]));
}
}

View File

@ -0,0 +1,179 @@
package com.onarandombox.MultiverseCore.displaytools;
import org.bukkit.ChatColor;
import org.bukkit.command.ConsoleCommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Various implementations of {@link DisplayHandler}.
*/
public class DisplayHandlers {
/**
* Standard list display.
*
* Supported settings: none.
*/
public static final DisplayHandler<Collection<String>> LIST = display -> display.getContents().stream()
.filter(display.getFilter()::checkMatch)
.map(s -> (ContentDisplay.LINE_BREAK.equals(s)) ? "" : display.getColorTool().get() + s)
.collect(Collectors.toList());
/**
* List display with paging.
*
* Supported settings: {@link DisplaySettings#SHOW_PAGE}, {@link DisplaySettings#LINES_PER_PAGE},
* {@link DisplaySettings#PAGE_IN_CONSOLE}, {@link DisplaySettings#DO_END_PADDING}.
*/
public static final DisplayHandler<Collection<String>> PAGE_LIST = new DisplayHandler<Collection<String>>() {
@Override
public Collection<String> format(@NotNull ContentDisplay<Collection<String>> display) throws DisplayFormatException {
if (dontNeedPaging(display)) {
return LIST.format(display);
}
int pages = 1;
int currentLength = 0;
int targetPage = display.getSetting(DisplaySettings.SHOW_PAGE);
int linesPerPage = display.getSetting(DisplaySettings.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(DisplaySettings.DO_END_PADDING)) {
IntStream.range(0, linesPerPage - content.size()).forEach(i -> content.add(""));
}
display.setSetting(DisplaySettings.TOTAL_PAGE, pages);
return content;
}
@Override
public void sendSubHeader(@NotNull ContentDisplay<Collection<String>> display) {
if (dontNeedPaging(display)) {
LIST.sendSubHeader(display);
return;
}
if (display.getFilter().hasFilter()) {
display.getSender().sendMessage(String.format("%s[ Page %s of %s, %s ]",
ChatColor.GRAY,
display.getSetting(DisplaySettings.SHOW_PAGE),
display.getSetting(DisplaySettings.TOTAL_PAGE),
display.getFilter().getFormattedString())
);
return;
}
display.getSender().sendMessage(String.format("%s[ Page %s of %s ]",
ChatColor.GRAY,
display.getSetting(DisplaySettings.SHOW_PAGE),
display.getSetting(DisplaySettings.TOTAL_PAGE))
);
}
private boolean dontNeedPaging(ContentDisplay<Collection<String>> display) {
return display.getSender() instanceof ConsoleCommandSender
&& !display.getSetting(DisplaySettings.PAGE_IN_CONSOLE);
}
};
/**
* Display a list inline.
*
* Supported settings: {@link DisplaySettings#SEPARATOR}.
*/
public static final DisplayHandler<Collection<String>> INLINE_LIST = display -> {
StringBuilder builder = new StringBuilder();
String separator = display.getSetting(DisplaySettings.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());
};
/**
* Display key value pair inline.
*
* Supported settings: {@link DisplaySettings#SEPARATOR}, {@link DisplaySettings#OPERATOR}.
*/
public static final DisplayHandler<Map<String, Object>> INLINE_MAP = display -> {
StringBuilder builder = new StringBuilder();
String separator = display.getSetting(DisplaySettings.SEPARATOR);
String operator = display.getSetting(DisplaySettings.OPERATOR);
for (Iterator<Entry<String, Object>> iterator = display.getContents().entrySet().iterator(); iterator.hasNext(); ) {
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());
};
}

View File

@ -0,0 +1,16 @@
package com.onarandombox.MultiverseCore.displaytools;
/**
* Represents a setting option that can be used by {@link DisplayHandler}.
*
* @param <T>
*/
public interface DisplaySetting<T> {
/**
* Gets the default value of this Display Setting.
*
* @return The default value.
*/
T defaultValue();
}

View File

@ -0,0 +1,44 @@
package com.onarandombox.MultiverseCore.displaytools;
import org.bukkit.ChatColor;
/**
* Collection of {@link DisplaySetting} that are used by various {@link DisplayHandler}.
*/
public class DisplaySettings {
/**
* 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;
/**
* Inline separator. E.g. '1, 2, 3'
*/
public static final DisplaySetting<String> SEPARATOR = () -> ChatColor.WHITE + ", ";
/**
* The thing between a key value pair. E.g. 'Me = Smart'
*/
public static final DisplaySetting<String> OPERATOR = () -> ChatColor.WHITE + " = ";
}