Mostly javadoc, and also some fixes there and there

This commit is contained in:
Marc Baloup 2022-08-10 03:04:12 +02:00
parent f976350ee1
commit 54bc8ab99a
Signed by: marcbal
GPG Key ID: BBC0FE3ABC30B893
42 changed files with 1671 additions and 733 deletions

View File

@ -1,16 +1,12 @@
package fr.pandacube.lib.bungee.commands; package fr.pandacube.lib.bungee.commands;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.suggestion.Suggestion; import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
import fr.pandacube.lib.commands.BrigadierCommand; import fr.pandacube.lib.commands.BrigadierCommand;
import fr.pandacube.lib.commands.SuggestionsSupplier; import fr.pandacube.lib.commands.SuggestionsSupplier;
import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
@ -18,18 +14,26 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor; import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* Abstract class that holds the logic of a specific command to be registered in {@link BungeeBrigadierDispatcher} and
* BungeeCord command API.
*/
public abstract class BungeeBrigadierCommand extends BrigadierCommand<CommandSender> { public abstract class BungeeBrigadierCommand extends BrigadierCommand<CommandSender> {
/**
* The command dispatcher.
*/
protected BungeeBrigadierDispatcher dispatcher; protected BungeeBrigadierDispatcher dispatcher;
/**
* Instanciate this command isntance.
* @param d the dispatcher in which to register this command.
*/
public BungeeBrigadierCommand(BungeeBrigadierDispatcher d) { public BungeeBrigadierCommand(BungeeBrigadierDispatcher d) {
if (d == null) { if (d == null) {
throw new IllegalStateException("BungeeBrigadierDispatcher not provided."); throw new IllegalStateException("BungeeBrigadierDispatcher not provided.");
@ -62,12 +66,14 @@ public abstract class BungeeBrigadierCommand extends BrigadierCommand<CommandSen
.redirect(commandNode) .redirect(commandNode)
.build() .build()
); );
ProxyServer.getInstance().getPluginManager().registerCommand(dispatcher.plugin, new CommandRelay(alias)); ProxyServer.getInstance().getPluginManager().registerCommand(dispatcher.plugin, new CommandRelay(alias));
} }
} }
private class CommandRelay extends Command implements TabExecutor { private class CommandRelay extends Command implements TabExecutor {
private final String alias; private final String alias;
public CommandRelay(String alias) { public CommandRelay(String alias) {
@ -117,9 +123,11 @@ public abstract class BungeeBrigadierCommand extends BrigadierCommand<CommandSen
} }
/**
* Wraps the provided {@link SuggestionsSupplier} into a Brigadiers {@link SuggestionProvider}.
* @param suggestions the suggestions to wrap.
* @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}.
*/
protected SuggestionProvider<CommandSender> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) { protected SuggestionProvider<CommandSender> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) {
return wrapSuggestions(suggestions, Function.identity()); return wrapSuggestions(suggestions, Function.identity());
} }

View File

@ -1,21 +1,7 @@
package fr.pandacube.lib.bungee.commands; package fr.pandacube.lib.bungee.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.context.SuggestionContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import fr.pandacube.lib.chat.Chat; import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.commands.BrigadierCommand;
import fr.pandacube.lib.commands.BrigadierDispatcher; import fr.pandacube.lib.commands.BrigadierDispatcher;
import fr.pandacube.lib.commands.BrigadierSuggestionsUtil;
import fr.pandacube.lib.util.Log;
import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.ComponentLike;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
@ -25,11 +11,10 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import java.util.ArrayList; /**
import java.util.Collection; * Implementation of {@link BrigadierDispatcher} that integrates the commands into BungeeCord API, so the players and
import java.util.List; * the console can actually execute them.
import java.util.concurrent.CompletableFuture; */
public class BungeeBrigadierDispatcher extends BrigadierDispatcher<CommandSender> implements Listener { public class BungeeBrigadierDispatcher extends BrigadierDispatcher<CommandSender> implements Listener {
@ -37,15 +22,20 @@ public class BungeeBrigadierDispatcher extends BrigadierDispatcher<CommandSender
/* package */ final Plugin plugin; /* package */ final Plugin plugin;
/**
* Create a new instance of {@link BungeeBrigadierDispatcher}.
* @param pl the plugin that creates this dispatcher.
*/
public BungeeBrigadierDispatcher(Plugin pl) { public BungeeBrigadierDispatcher(Plugin pl) {
plugin = pl; plugin = pl;
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
} }
/**
* Called when a player sends a chat message. Used to gets the typed command and execute it.
* @param event the event.
*/
@EventHandler @EventHandler
public void onChat(ChatEvent event) { public void onChat(ChatEvent event) {
if (!event.getMessage().startsWith("/")) if (!event.getMessage().startsWith("/"))

View File

@ -1,8 +1,10 @@
package fr.pandacube.lib.bungee.permissions; package fr.pandacube.lib.bungee.permissions;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBConnection;
import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.players.standalone.StandaloneOnlinePlayer; import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
import fr.pandacube.lib.players.standalone.StandalonePlayerManager; import fr.pandacube.lib.players.standalone.AbstractPlayerManager;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@ -12,13 +14,31 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import java.util.function.Function;
/**
* Class that integrates the {@code pandalib-permissions} system into a BungeeCord instance.
* To register the event listener into BungeeCord, use {@link #init(Plugin)}.
* The permission system must be initialized first, using {@link Permissions#init(Function)}.
* Dont forget that the permission system also needs a connection to a database, so dont forget to call
* {@link DB#init(DBConnection, String)} with the appropriate parameters before anything.
*/
public class PandalibBungeePermissions implements Listener { public class PandalibBungeePermissions implements Listener {
/**
* Registers event listener to redirect permission checks to {@code pandalib-permissions}.
* @param bungeePlugin a BungeeCord plugin.
*/
public static void init(Plugin bungeePlugin) { public static void init(Plugin bungeePlugin) {
ProxyServer.getInstance().getPluginManager().registerListener(bungeePlugin, new PandalibBungeePermissions()); ProxyServer.getInstance().getPluginManager().registerListener(bungeePlugin, new PandalibBungeePermissions());
} }
/**
* Event handler called when a plugin asks if a player has a permission.
* @param event the permission check event.
*/
@EventHandler(priority = Byte.MAX_VALUE) @EventHandler(priority = Byte.MAX_VALUE)
public void onPermissionCheck(PermissionCheckEvent event) public void onPermissionCheck(PermissionCheckEvent event)
{ {
@ -37,9 +57,9 @@ public class PandalibBungeePermissions implements Listener {
String world = null; String world = null;
if (tryPermPlayerManager) { if (tryPermPlayerManager) {
try { try {
StandalonePlayerManager<?, ?> pm = StandalonePlayerManager.getInstance(); AbstractPlayerManager<?, ?> pm = AbstractPlayerManager.getInstance();
if (pm != null) { if (pm != null) {
StandaloneOnlinePlayer op = pm.get(p.getUniqueId()); AbstractOnlinePlayer op = pm.get(p.getUniqueId());
if (op != null) { if (op != null) {
world = op.getWorldName(); world = op.getWorldName();
} }

View File

@ -3,9 +3,9 @@ package fr.pandacube.lib.bungee.players;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import fr.pandacube.lib.players.standalone.StandaloneOffPlayer; import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
public interface BungeeOffPlayer extends StandaloneOffPlayer { public interface BungeeOffPlayer extends AbstractOffPlayer {
/* /*
* Related class instances * Related class instances

View File

@ -19,11 +19,11 @@ import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.PluginMessage;
import fr.pandacube.lib.chat.Chat; import fr.pandacube.lib.chat.Chat;
import fr.pandacube.lib.players.standalone.StandaloneOnlinePlayer; import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
import fr.pandacube.lib.reflect.Reflect; import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.util.MinecraftVersion; import fr.pandacube.lib.util.MinecraftVersion;
public interface BungeeOnlinePlayer extends BungeeOffPlayer, StandaloneOnlinePlayer { public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlayer {
/* /*
* General data and state * General data and state
@ -134,7 +134,7 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, StandaloneOnlinePla
return new BungeeClientOptions(this); return new BungeeClientOptions(this);
} }
class BungeeClientOptions implements StandaloneOnlinePlayer.ClientOptions { class BungeeClientOptions implements AbstractOnlinePlayer.ClientOptions {
private final BungeeOnlinePlayer op; private final BungeeOnlinePlayer op;
public BungeeClientOptions(BungeeOnlinePlayer op) { public BungeeClientOptions(BungeeOnlinePlayer op) {

View File

@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* A tree structure of {@link Chat} component intended to be rendered using {@link #render(boolean)}. * A tree structure of {@link Chat} component intended to be rendered in chat using {@link #render(boolean)}.
*/ */
public class ChatTreeNode { public class ChatTreeNode {

View File

@ -6,91 +6,111 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.ParsedCommandNode; import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
/** /**
* Abstract class that holds the logic of a specific command to be integrated in a Brigadier command dispatcher. * Abstract class that holds the logic of a specific command to be integrated in a Brigadier command dispatcher.
* Subclasses may use any mechanism to integrate this command in the environments Brigadier instance, during the * Subclasses may use any mechanism to integrate this command in the environments Brigadier instance (or in a
* instantiation of this object. * {@link BrigadierDispatcher} instance), during the instantiation of this object.
* @param <S> the command source (or command sender) type. * @param <S> the command source (or command sender) type.
*/ */
public abstract class BrigadierCommand<S> { public abstract class BrigadierCommand<S> {
/**
* Returns a builder for this command.
* Concrete class should include any element in the builder that is needed to build the command (sub-commands and
* arguments, requirements, redirection, ...).
* If any of the sub-commands and arguments needs to know the {@link LiteralCommandNode} builded from the returned
* {@link LiteralArgumentBuilder}, this can be done by overriding {@link #postBuildCommand(LiteralCommandNode)}.
* @return a builder for this command.
*/
protected abstract LiteralArgumentBuilder<S> buildCommand(); protected abstract LiteralArgumentBuilder<S> buildCommand();
/** /**
* Method to implement if the reference to the command node has to be known when building the subcommands. * Method to override if the reference to the command node has to be known when building the subcommands.
* @param commandNode the command node builded from {@link #buildCommand()}. * @param commandNode the command node builded from {@link #buildCommand()}.
*/ */
protected void postBuildCommand(LiteralCommandNode<S> commandNode) { protected void postBuildCommand(LiteralCommandNode<S> commandNode) {
// default implementation does nothing. // default implementation does nothing.
} }
/**
* Method to override if this command have any aliases.
* @return an array of string corresponding to the aliases. This must not include the orignal command name (that
* is the name of the literal command node builded from {@link #buildCommand()}).
*/
protected String[] getAliases() { protected String[] getAliases() {
return new String[0]; return new String[0];
} }
/**
* Creates a new {@link LiteralArgumentBuilder} that has the provided name.
* @param name the name of the command node.
* @return a new {@link LiteralArgumentBuilder} that has the provided name.
*/
public LiteralArgumentBuilder<S> literal(String name) { public LiteralArgumentBuilder<S> literal(String name) {
return LiteralArgumentBuilder.literal(name); return LiteralArgumentBuilder.literal(name);
} }
/**
* Creates a new {@link RequiredArgumentBuilder} that has the provided name.
* @param name the name of the command node.
* @param type the type of the argument.
* @param <T> the argument type.
* @return a new {@link RequiredArgumentBuilder} that has the provided name.
*/
public <T> RequiredArgumentBuilder<S, T> argument(String name, ArgumentType<T> type) { public <T> RequiredArgumentBuilder<S, T> argument(String name, ArgumentType<T> type) {
return RequiredArgumentBuilder.argument(name, type); return RequiredArgumentBuilder.argument(name, type);
} }
/**
* Tells if the provided command sender is a player.
* @param sender the sender to test if its a player or not.
* @return true if the sender is a player, false otherwise.
*/
public abstract boolean isPlayer(S sender); public abstract boolean isPlayer(S sender);
/**
* Tells if the provided command sender is the console.
* @param sender the sender to test if its the console or not.
* @return true if the sender is the console, false otherwise.
*/
public abstract boolean isConsole(S sender); public abstract boolean isConsole(S sender);
/**
* Provides a {@link Predicate} that tests if the command sender is a player.
* @return a {@link Predicate} that tests if the command sender is a player.
* @see #isPlayer(Object)
*/
public Predicate<S> isPlayer() { public Predicate<S> isPlayer() {
return this::isPlayer; return this::isPlayer;
} }
/**
* Provides a {@link Predicate} that tests if the command sender is the console.
* @return a {@link Predicate} that tests if the command sender is the console.
* @see #isConsole(Object)
*/
public Predicate<S> isConsole() { public Predicate<S> isConsole() {
return this::isConsole; return this::isConsole;
} }
/**
* Provides a {@link Predicate} that tests if the command sender has the provided permission.
* @param permission the permission tested by the returned predicate on the command sender.
* @return a {@link Predicate} that tests if the command sender has the provided permission.
*/
public abstract Predicate<S> hasPermission(String permission); public abstract Predicate<S> hasPermission(String permission);
@ -99,8 +119,15 @@ public abstract class BrigadierCommand<S> {
/**
* Determines if the literal node is found in the command.
* <p>
* <b>Be aware that this method search even beyond any fork or redirection, so it may encounter literal nodes that
* have the provided name but belong to other commands, so it can produce a false positive.</b>
* @param context the context of the command execution.
* @param literal the literal command node to search for in the typed command.
* @return true if the provided literal is in the typed command, false otherwise.
*/
public static boolean isLiteralParsed(CommandContext<?> context, String literal) { public static boolean isLiteralParsed(CommandContext<?> context, String literal) {
for (ParsedCommandNode<?> node : context.getNodes()) { for (ParsedCommandNode<?> node : context.getNodes()) {
if (node.getNode() instanceof LiteralCommandNode<?> lNode if (node.getNode() instanceof LiteralCommandNode<?> lNode
@ -111,24 +138,60 @@ public abstract class BrigadierCommand<S> {
} }
/**
* Gets the argument value from the provided context, silently returning null (instead of throwing an exception)
* if the argument is not found.
* @param context the context of the command execution.
* @param argument the argument to search for.
* @param type the type of the argument.
* @param <T> the argument type.
* @return the value of the argument, or null if not found.
*/
public static <T> T tryGetArgument(CommandContext<?> context, String argument, Class<T> type) { public static <T> T tryGetArgument(CommandContext<?> context, String argument, Class<T> type) {
return tryGetArgument(context, argument, type, Function.identity(), null); return tryGetArgument(context, argument, type, Function.identity(), null);
} }
public static <ST, T> T tryGetArgument(CommandContext<?> context, String argument, Class<ST> sourceType, Function<ST, T> transformIfFound) { /**
return tryGetArgument(context, argument, sourceType, transformIfFound, null); * Gets the argument value from the provided context, silently returning a default value (instead of throwing an
} * exception) if the argument is not found.
* @param context the context of the command execution.
* @param argument the argument to search for.
* @param type the type of the argument.
* @param deflt the default value if not found.
* @param <T> the argument type.
* @return the value of the argument, or {@code deflt} if not found.
*/
public static <T> T tryGetArgument(CommandContext<?> context, String argument, Class<T> type, T deflt) { public static <T> T tryGetArgument(CommandContext<?> context, String argument, Class<T> type, T deflt) {
return tryGetArgument(context, argument, type, Function.identity(), deflt); return tryGetArgument(context, argument, type, Function.identity(), deflt);
} }
/**
* Gets the argument value from the provided context and transform it using the provided function, or silently
* returning null (instead of throwing an exception) if the argument is not found.
* @param context the context of the command execution.
* @param argument the argument to search for.
* @param sourceType the type of the argument in the command context.
* @param transformIfFound the function to transform the argument value before returning.
* @param <ST> the argument type in the command context.
* @param <T> the returned type.
* @return the value of the argument, transformed by {@code transformIfFound}, or null if not found.
*/
public static <ST, T> T tryGetArgument(CommandContext<?> context, String argument, Class<ST> sourceType, Function<ST, T> transformIfFound) {
return tryGetArgument(context, argument, sourceType, transformIfFound, null);
}
/**
* Gets the argument value from the provided context and transform it using the provided function, or silently
* returning a default value (instead of throwing an exception) if the argument is not found.
* @param context the context of the command execution.
* @param argument the argument to search for.
* @param sourceType the type of the argument in the command context.
* @param transformIfFound the function to transform the argument value before returning.
* @param deflt the default value if not found.
* @param <ST> the argument type in the command context.
* @param <T> the returned type.
* @return the value of the argument, transformed by {@code transformIfFound}, or {@code deflt} if not found.
*/
public static <ST, T> T tryGetArgument(CommandContext<?> context, String argument, Class<ST> sourceType, Function<ST, T> transformIfFound, T deflt) { public static <ST, T> T tryGetArgument(CommandContext<?> context, String argument, Class<ST> sourceType, Function<ST, T> transformIfFound, T deflt) {
ST sourceValue; ST sourceValue;
try { try {
@ -140,25 +203,25 @@ public abstract class BrigadierCommand<S> {
} }
/**
* Creates a new instance of {@link CommandSyntaxException} with the provided message.
* @param message the exception message.
* @return a new instance of {@link CommandSyntaxException} with the provided message.
*/
public static CommandSyntaxException newCommandException(String message) { public static CommandSyntaxException newCommandException(String message) {
return new SimpleCommandExceptionType(new LiteralMessage(message)).create(); return new SimpleCommandExceptionType(new LiteralMessage(message)).create();
} }
/**
* Wraps the provided {@link SuggestionsSupplier} into a Brigadiers {@link SuggestionProvider}.
* @param suggestions the suggestions to wrap.
* @param senderUnwrapper function to convert the command sender provided by brigadier into the command sender
* supported by {@link SuggestionsSupplier}.
* @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}.
* @param <AS> the type of command sender supported by the {@link SuggestionsSupplier}.
*/
protected <AS> SuggestionProvider<S> wrapSuggestions(SuggestionsSupplier<AS> suggestions, Function<S, AS> senderUnwrapper) { protected <AS> SuggestionProvider<S> wrapSuggestions(SuggestionsSupplier<AS> suggestions, Function<S, AS> senderUnwrapper) {
return (context, builder) -> { return (context, builder) -> {
AS sender = senderUnwrapper.apply(context.getSource()); AS sender = senderUnwrapper.apply(context.getSource());

View File

@ -11,28 +11,41 @@ import net.kyori.adventure.text.ComponentLike;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/**
* Abstract class that holds a Brigadier {@link CommandDispatcher} instance.
* Subclasses contains logic to integrate this commands dispatcher into their environment (like Bungee or CLI app).
* @param <S> the command source (or command sender) type.
*/
public abstract class BrigadierDispatcher<S> { public abstract class BrigadierDispatcher<S> {
private final CommandDispatcher<S> dispatcher = new CommandDispatcher<>(); private final CommandDispatcher<S> dispatcher = new CommandDispatcher<>();
/**
* Registers the provided command node into this dispatcher.
* @param node the node to register.
*/
public void register(LiteralCommandNode<S> node) { public void register(LiteralCommandNode<S> node) {
dispatcher.getRoot().addChild(node); dispatcher.getRoot().addChild(node);
} }
/**
* Returns the Brigadier dispatcher.
* @return the Brigadier dispatcher.
*/
public CommandDispatcher<S> getDispatcher() { public CommandDispatcher<S> getDispatcher() {
return dispatcher; return dispatcher;
} }
/**
* Executes the provided command as the provided sender.
* @param sender the command sender.
* @param commandWithoutSlash the command, without the eventual slash at the begining.
* @return the value returned by the executed command.
*/
public int execute(S sender, String commandWithoutSlash) { public int execute(S sender, String commandWithoutSlash) {
ParseResults<S> parsed = dispatcher.parse(commandWithoutSlash, sender); ParseResults<S> parsed = dispatcher.parse(commandWithoutSlash, sender);
@ -46,11 +59,15 @@ public abstract class BrigadierDispatcher<S> {
Log.severe(e); Log.severe(e);
return 0; return 0;
} }
} }
/**
* Gets the suggestions for the currenlty being typed command.
* @param sender the command sender.
* @param buffer the command that is being typed.
* @return the suggestions for the currenlty being typed command.
*/
public Suggestions getSuggestions(S sender, String buffer) { public Suggestions getSuggestions(S sender, String buffer) {
ParseResults<S> parsed = dispatcher.parse(buffer, sender); ParseResults<S> parsed = dispatcher.parse(buffer, sender);
try { try {
@ -64,10 +81,11 @@ public abstract class BrigadierDispatcher<S> {
} }
/**
* Sends the provided message to the sender.
* @param sender the sender to send the message to.
* @param message the message to send.
*/
protected abstract void sendSenderMessage(S sender, ComponentLike message); protected abstract void sendSenderMessage(S sender, ComponentLike message);
} }

View File

@ -1,5 +1,6 @@
package fr.pandacube.lib.commands; package fr.pandacube.lib.commands;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.context.StringRange;
@ -22,7 +23,16 @@ import java.util.concurrent.CompletableFuture;
public class BrigadierSuggestionsUtil { public class BrigadierSuggestionsUtil {
/**
* Gets suggestions with the provided parsed command.
* <p>
* This is a reimplementation of {@link CommandDispatcher#getCompletionSuggestions(ParseResults, int)} that
* keeps the original ordering of the suggestions.
* @param parsed the parsed command.
* @return the suggestions.
* @param <S> the sender type.
* @see CommandDispatcher#getCompletionSuggestions(ParseResults, int)
*/
public static <S> CompletableFuture<Suggestions> buildSuggestionBrigadier(ParseResults<S> parsed) { public static <S> CompletableFuture<Suggestions> buildSuggestionBrigadier(ParseResults<S> parsed) {
int cursor = parsed.getReader().getTotalLength(); int cursor = parsed.getReader().getTotalLength();
final CommandContextBuilder<S> context = parsed.getContext(); final CommandContextBuilder<S> context = parsed.getContext();
@ -55,7 +65,17 @@ public class BrigadierSuggestionsUtil {
return result; return result;
} }
// inspired from com.mojang.brigadier.suggestion.Suggestions#merge, but without the sorting part
/**
* Merges the provided {@link Suggestions}.
* <p>
* This is a reimplementation of {@link Suggestions#merge(String, Collection)} that keeps the original ordering of
* the suggestions.
* @param input the command on which are based the suggestions.
* @param suggestions the {@link Suggestions} to merge.
* @return a {@link Suggestions}.
* @see Suggestions#create(String, Collection)
*/
public static Suggestions mergeSuggestionsOriginalOrdering(String input, Collection<Suggestions> suggestions) { public static Suggestions mergeSuggestionsOriginalOrdering(String input, Collection<Suggestions> suggestions) {
if (suggestions.isEmpty()) { if (suggestions.isEmpty()) {
return new Suggestions(StringRange.at(0), new ArrayList<>(0)); return new Suggestions(StringRange.at(0), new ArrayList<>(0));
@ -72,8 +92,16 @@ public class BrigadierSuggestionsUtil {
/**
// inspired from com.mojang.brigadier.suggestion.Suggestions#create, but without the sorting part * Creates a {@link Suggestions} from the provided Collection of {@link Suggestion}.
* <p>
* This is a reimplementation of {@link Suggestions#create(String, Collection)} that keeps the original ordering of
* the suggestions.
* @param command the command on which are based the suggestions.
* @param suggestions the Collection of {@link Suggestion}.
* @return a {@link Suggestions}.
* @see Suggestions#create(String, Collection)
*/
public static Suggestions createSuggestionsOriginalOrdering(String command, Collection<Suggestion> suggestions) { public static Suggestions createSuggestionsOriginalOrdering(String command, Collection<Suggestion> suggestions) {
if (suggestions.isEmpty()) { if (suggestions.isEmpty()) {
return new Suggestions(StringRange.at(0), new ArrayList<>(0)); return new Suggestions(StringRange.at(0), new ArrayList<>(0));
@ -96,7 +124,7 @@ public class BrigadierSuggestionsUtil {
public static CompletableFuture<Suggestions> completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) { /* package */ static CompletableFuture<Suggestions> completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) {
return CompletableFuture.completedFuture( return CompletableFuture.completedFuture(
createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder)) createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder))
); );

View File

@ -19,6 +19,11 @@
<artifactId>pandalib-util</artifactId> <artifactId>pandalib-util</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>fr.pandacube.lib</groupId>
<artifactId>pandalib-reflect</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId> <artifactId>commons-dbcp2</artifactId>

View File

@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
/** /**
@ -576,7 +577,7 @@ public final class DB {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws DBException { private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws DBException {
try { try {
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id")); E instance = Reflect.ofClass(elemClass).constructor(int.class).instanciate(set.getInt("id"));
int fieldCount = set.getMetaData().getColumnCount(); int fieldCount = set.getMetaData().getColumnCount();

View File

@ -8,6 +8,8 @@ import java.util.concurrent.ExecutionException;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import fr.pandacube.lib.db.DB;
import fr.pandacube.lib.db.DBConnection;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -23,12 +25,26 @@ import org.bukkit.plugin.java.JavaPlugin;
import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
/**
* Class that integrates the {@code pandalib-permissions} system into a Bukkit/Spigot/Paper instance.
* The integration is made when calling {@link #init(JavaPlugin, String)}.
* The permission system must be initialized first, using {@link Permissions#init(Function)}.
* Dont forget that the permission system also needs a connection to a database, so dont forget to call
* {@link DB#init(DBConnection, String)} with the appropriate parameters before anything.
*/
public class PandalibPaperPermissions implements Listener { public class PandalibPaperPermissions implements Listener {
/* package */ static JavaPlugin plugin; /* package */ static JavaPlugin plugin;
/* package */ static String serverName; /* package */ static String serverName;
/* package */ static final Map<String, String> permissionMap = new HashMap<>(); /* package */ static final Map<String, String> permissionMap = new HashMap<>();
/**
* Integrates the {@code pandalib-permissions} system into the Bukkit server.
* @param plugin a Bukkit plugin.
* @param serverName the name of the current server, used to fetch server specific permissions. Cannot be null.
* If this server in not in a multi-server configuration, use a dummy server name, like
* {@code ""} (empty string).
*/
public static void init(JavaPlugin plugin, String serverName) { public static void init(JavaPlugin plugin, String serverName) {
PandalibPaperPermissions.plugin = plugin; PandalibPaperPermissions.plugin = plugin;
PandalibPaperPermissions.serverName = serverName; PandalibPaperPermissions.serverName = serverName;
@ -58,6 +74,10 @@ public class PandalibPaperPermissions implements Listener {
} }
} }
/**
* Player login event handler.
* @param event the event.
*/
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent event) { public void onPlayerLogin(PlayerLoginEvent event) {
Permissions.clearPlayerCache(event.getPlayer().getUniqueId()); Permissions.clearPlayerCache(event.getPlayer().getUniqueId());
@ -67,6 +87,10 @@ public class PandalibPaperPermissions implements Listener {
} }
/**
* Player quit event handler.
* @param event the event.
*/
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
PermissionsInjectorBukkit.uninject(event.getPlayer()); PermissionsInjectorBukkit.uninject(event.getPlayer());

View File

@ -29,7 +29,7 @@ import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.reflect.Reflect; import fr.pandacube.lib.reflect.Reflect;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
public class PermissionsInjectorBukkit /* package */ class PermissionsInjectorBukkit
{ {
// to be called : onEnable for console, onPlayerLogin (not Join) (Priority LOWEST) for players // to be called : onEnable for console, onPlayerLogin (not Join) (Priority LOWEST) for players

View File

@ -10,7 +10,7 @@ import fr.pandacube.lib.permissions.PermGroup;
import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
public class PermissionsInjectorVault { /* package */ class PermissionsInjectorVault {
public static PandaVaultPermission permInstance; public static PandaVaultPermission permInstance;
@ -32,7 +32,7 @@ public class PermissionsInjectorVault {
public static class PandaVaultPermission extends net.milkbowl.vault.permission.Permission { /* package */ static class PandaVaultPermission extends net.milkbowl.vault.permission.Permission {
private PandaVaultPermission() { } private PandaVaultPermission() { }

View File

@ -13,13 +13,11 @@ import fr.pandacube.lib.permissions.PermPlayer;
import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
public class PermissionsInjectorWEPIF { /* package */ class PermissionsInjectorWEPIF {
public static PandaWEPIFPermissionsProvider permInstance;
public static void inject() { public static void inject() {
try { try {
permInstance = new PandaWEPIFPermissionsProvider(); PandaWEPIFPermissionsProvider permInstance = new PandaWEPIFPermissionsProvider();
Bukkit.getServicesManager().register(com.sk89q.wepif.PermissionsProvider.class, permInstance, Bukkit.getServicesManager().register(com.sk89q.wepif.PermissionsProvider.class, permInstance,
PandalibPaperPermissions.plugin, ServicePriority.Highest); PandalibPaperPermissions.plugin, ServicePriority.Highest);
Log.info("Providing permissions through WEPIF"); Log.info("Providing permissions through WEPIF");
@ -36,7 +34,7 @@ public class PermissionsInjectorWEPIF {
public static class PandaWEPIFPermissionsProvider implements com.sk89q.wepif.PermissionsProvider { /* package */ static class PandaWEPIFPermissionsProvider implements com.sk89q.wepif.PermissionsProvider {
private PandaWEPIFPermissionsProvider() { } private PandaWEPIFPermissionsProvider() { }
private PermPlayer getPlayer(OfflinePlayer player) { private PermPlayer getPlayer(OfflinePlayer player) {

View File

@ -5,9 +5,9 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scoreboard.Team; import org.bukkit.scoreboard.Team;
import fr.pandacube.lib.players.standalone.StandaloneOffPlayer; import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
public interface PaperOffPlayer extends StandaloneOffPlayer { public interface PaperOffPlayer extends AbstractOffPlayer {
/* /*
* General data and state * General data and state

View File

@ -21,9 +21,9 @@ import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.MainHand; import org.bukkit.inventory.MainHand;
import fr.pandacube.lib.players.standalone.StandaloneOnlinePlayer; import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
public interface PaperOnlinePlayer extends PaperOffPlayer, StandaloneOnlinePlayer { public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer {
/* /*
* General data and state * General data and state
@ -119,7 +119,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, StandaloneOnlinePlaye
@Override @Override
PaperClientOptions getClientOptions(); PaperClientOptions getClientOptions();
abstract class PaperClientOptions implements StandaloneOnlinePlayer.ClientOptions { abstract class PaperClientOptions implements AbstractOnlinePlayer.ClientOptions {
private final PaperOnlinePlayer op; private final PaperOnlinePlayer op;

View File

@ -16,6 +16,6 @@ public class ServerChunkCache extends ReflectWrapper {
protected ServerChunkCache(Object obj) { protected ServerChunkCache(Object obj) {
super(obj); super(obj);
chunkMap = wrap(wrapReflectEx(() -> FIELD_chunkMap.getValue(obj))); chunkMap = wrap(wrapReflectEx(() -> FIELD_chunkMap.getValue(obj)), ChunkMap.class);
} }
} }

View File

@ -65,13 +65,14 @@
<exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude> <exclude>META-INF/*.RSA</exclude>
<exclude>META-INF/MANIFEDT.MF</exclude>
</excludes> </excludes>
</filter> </filter>
</filters> </filters>
<relocations> <relocations>
<relocation> <relocation>
<pattern>com.fathzer.soft.javaluator</pattern> <pattern>com.fathzer.soft.javaluator</pattern>
<shadedPattern>fr.pandacube.shaded.javaluator</shadedPattern> <shadedPattern>fr.pandacube.lib.permissions.shaded.javaluator</shadedPattern>
</relocation> </relocation>
</relocations> </relocations>
</configuration> </configuration>

View File

@ -8,31 +8,56 @@ import java.util.Set;
import java.util.stream.LongStream; import java.util.stream.LongStream;
import fr.pandacube.lib.chat.ChatTreeNode; import fr.pandacube.lib.chat.ChatTreeNode;
import fr.pandacube.lib.permissions.PermissionExpressionParser.LitteralPermissionTester;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedEntity; import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedEntity;
import fr.pandacube.lib.permissions.SQLPermissions.EntityType; import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
public abstract class PermEntity { /**
protected final String name; * Represents an entity in the permission system, either a group or a player.
protected final EntityType type; */
protected PermEntity(String n, EntityType t) { public sealed abstract class PermEntity permits PermPlayer, PermGroup {
/* package */ final String name;
/* package */ final EntityType type;
/* package */ PermEntity(String n, EntityType t) {
name = n; type = t; name = n; type = t;
} }
protected abstract CachedEntity getBackendEntity(); /* package */ abstract CachedEntity getBackendEntity();
/**
* Gets all the groups this entity inherits from.
* @return a list of all the groups this entity inherits from.
*/
public abstract List<PermGroup> getInheritances(); public abstract List<PermGroup> getInheritances();
/**
* Gets all the group names this entity inherits from.
* @return a list of all the group names this entity inherits from.
*/
public abstract List<String> getInheritancesString(); public abstract List<String> getInheritancesString();
/**
* Gets the name of this entity.
* @return the name of this entity.
*/
public abstract String getName(); public abstract String getName();
/**
* Gets the name of this entity, as it is stored in the database.
* @return the name of this entity, as it is stored in the database.
*/
public String getInternalName() { public String getInternalName() {
return name; return name;
} }
/** /**
* Tells if the current entity inherits directly or indirectly from the specified group * Tells if the current entity inherits directly or indirectly from the specified group.
* @param group the group to search for * @param group the group to search for
* @param recursive true to search in the inheritance tree, or false to search only in the inheritance list of the current entity. * @param recursive true to search in the inheritance tree, or false to search only in the inheritance list of the current entity.
* @return true if the current entity inherits directly or indirectly from the specified group, false otherwise.
*/ */
public boolean inheritsFromGroup(String group, boolean recursive) { public boolean inheritsFromGroup(String group, boolean recursive) {
if (group == null) if (group == null)
@ -40,66 +65,162 @@ public abstract class PermEntity {
return getInheritances().stream().anyMatch(g -> g.name.equals(group) || (recursive && g.inheritsFromGroup(group, true))); return getInheritances().stream().anyMatch(g -> g.name.equals(group) || (recursive && g.inheritsFromGroup(group, true)));
} }
/**
* Gets the effective prefix of this entity.
* It is either the prefix defined directly for this entity, or from inheritance.
* @return the effective prefix of this entity.
*/
public String getPrefix() { public String getPrefix() {
return Permissions.resolver.getEffectivePrefix(name, type); return Permissions.resolver.getEffectivePrefix(name, type);
} }
/**
* Gets the prefix defined directly for this entity.
* @return the prefix defined directly for this entity.
*/
public String getSelfPrefix() { public String getSelfPrefix() {
return getBackendEntity().getSelfPrefix(); return getBackendEntity().getSelfPrefix();
} }
/**
* Provides informations on how the effective prefix of this entity is determined.
* @return a {@link ChatTreeNode} providing informations on how the effective prefix of this entity is determined.
*/
public ChatTreeNode debugPrefix() { public ChatTreeNode debugPrefix() {
return Permissions.resolver.debugPrefix(name, type); return Permissions.resolver.debugPrefix(name, type);
} }
/**
* Sets the prefix of this entity.
* @param prefix the prefix for this entity.
*/
public void setSelfPrefix(String prefix) { public void setSelfPrefix(String prefix) {
Permissions.backendWriter.setSelfPrefix(name, type, prefix); Permissions.backendWriter.setSelfPrefix(name, type, prefix);
} }
/**
* Gets the effective suffix of this entity.
* It is either the suffix defined directly for this entity, or from inheritance.
* @return the effective suffix of this entity.
*/
public String getSuffix() { public String getSuffix() {
return Permissions.resolver.getEffectiveSuffix(name, type); return Permissions.resolver.getEffectiveSuffix(name, type);
} }
/**
* Gets the suffix defined directly for this entity.
* @return the suffix defined directly for this entity.
*/
public String getSelfSuffix() { public String getSelfSuffix() {
return getBackendEntity().getSelfSuffix(); return getBackendEntity().getSelfSuffix();
} }
/**
* Provides informations on how the effective suffix of this entity is determined.
* @return a {@link ChatTreeNode} providing informations on how the effective suffix of this entity is determined.
*/
public ChatTreeNode debugSuffix() { public ChatTreeNode debugSuffix() {
return Permissions.resolver.debugSuffix(name, type); return Permissions.resolver.debugSuffix(name, type);
} }
/**
* Sets the suffix of this entity.
* @param suffix the suffix for this entity.
*/
public void setSelfSuffix(String suffix) { public void setSelfSuffix(String suffix) {
Permissions.backendWriter.setSelfSuffix(name, type, suffix); Permissions.backendWriter.setSelfSuffix(name, type, suffix);
} }
/**
* Gets the effective list of permissions that applies to this entity out of a specific server and world.
* It is either the permissions defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @return the effective list of permissions that applies to this entity out of a specific server and world.
*/
public Map<String, Boolean> listEffectivePermissions() { public Map<String, Boolean> listEffectivePermissions() {
return listEffectivePermissions(null, null); return listEffectivePermissions(null, null);
} }
/**
* Gets the effective list of permissions that applies to this entity on a specific server.
* It is either the permissions defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param server the server where the returned permissions apply for this entity.
* @return the effective list of permissions that applies to this entity on a specific server.
*/
public Map<String, Boolean> listEffectivePermissions(String server) { public Map<String, Boolean> listEffectivePermissions(String server) {
return listEffectivePermissions(server, null); return listEffectivePermissions(server, null);
} }
/**
* Gets the effective list of permissions that applies to this entity on a specific server and world.
* It is either the permissions defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param server the server containing the world where the returned permissions apply for this entity.
* @param world the world in the server where the returned permissions apply for this entity.
* @return the effective list of permissions that applies to this entity on a specific server and world.
*/
public Map<String, Boolean> listEffectivePermissions(String server, String world) { public Map<String, Boolean> listEffectivePermissions(String server, String world) {
return Permissions.resolver.getEffectivePermissionList(name, type, server, world); return Permissions.resolver.getEffectivePermissionList(name, type, server, world);
} }
/**
* Gets the effective values of the provided permission range prefix that applies to this entity out of a specific
* server and world.
* It is either the range values defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param permissionPrefix the permission range prefix.
* @return the effective values of the provided permission range prefix that applies to this entity out of a
* specific server and world.
*/
public LongStream getPermissionRangeValues(String permissionPrefix) { public LongStream getPermissionRangeValues(String permissionPrefix) {
return getPermissionRangeValues(permissionPrefix, null, null); return getPermissionRangeValues(permissionPrefix, null, null);
} }
/**
* Gets the effective values of the provided permission range prefix that applies to this entity on a specific
* server.
* It is either the range values defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param permissionPrefix the permission range prefix.
* @param server the server where the returned values apply for this entity.
* @return the effective values of the provided permission range prefix that applies to this entity on a specific
* server.
*/
public LongStream getPermissionRangeValues(String permissionPrefix, String server) { public LongStream getPermissionRangeValues(String permissionPrefix, String server) {
return getPermissionRangeValues(permissionPrefix, server, null); return getPermissionRangeValues(permissionPrefix, server, null);
} }
/**
* Gets the effective values of the provided permission range prefix that applies to this entity on a specific
* server and world.
* It is either the range values defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param permissionPrefix the permission range prefix.
* @param server the server containing the world where the returned values apply for this entity.
* @param world the world in the server where the returned values apply for this entity.
* @return the effective values of the provided permission range prefix that applies to this entity on a specific
* server and world.
*/
public LongStream getPermissionRangeValues(String permissionPrefix, String server, String world) { public LongStream getPermissionRangeValues(String permissionPrefix, String server, String world) {
String prefixWithEndingDot = permissionPrefix.endsWith(".") ? permissionPrefix : (permissionPrefix + "."); String prefixWithEndingDot = permissionPrefix.endsWith(".") ? permissionPrefix : (permissionPrefix + ".");
int prefixLength = prefixWithEndingDot.length(); int prefixLength = prefixWithEndingDot.length();
@ -122,105 +243,298 @@ public abstract class PermEntity {
} }
/**
* Gets the maximum effective value of the provided permission range prefix that applies to this entity out of a
* specific server and world.
* It is either the range values defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param permissionPrefix the permission range prefix.
* @return the maximum effective value of the provided permission range prefix that applies to this entity out of a
* specific server and world.
*/
public OptionalLong getPermissionRangeMax(String permissionPrefix) { public OptionalLong getPermissionRangeMax(String permissionPrefix) {
return getPermissionRangeMax(permissionPrefix, null, null); return getPermissionRangeMax(permissionPrefix, null, null);
} }
/**
* Gets the maximum effective value of the provided permission range prefix that applies to this entity on a
* specific server.
* It is either the range values defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param permissionPrefix the permission range prefix.
* @param server the server where the returned value applies for this entity.
* @return the maximum effective value of the provided permission range prefix that applies to this entity on a
* specific server.
*/
public OptionalLong getPermissionRangeMax(String permissionPrefix, String server) { public OptionalLong getPermissionRangeMax(String permissionPrefix, String server) {
return getPermissionRangeMax(permissionPrefix, server, null); return getPermissionRangeMax(permissionPrefix, server, null);
} }
/**
* Gets the maximum effective value of the provided permission range prefix that applies to this entity on a
* specific server and world.
* It is either the range values defined directly for this entity, or from inheritance as long as they are not
* overriden.
* @param permissionPrefix the permission range prefix.
* @param server the server containing the world where the returned value applies for this entity.
* @param world the world in the server where the returned value applies for this entity.
* @return the maximum effective value of the provided permission range prefix that applies to this entity on a
* specific server and world.
*/
public OptionalLong getPermissionRangeMax(String permissionPrefix, String server, String world) { public OptionalLong getPermissionRangeMax(String permissionPrefix, String server, String world) {
return getPermissionRangeValues(permissionPrefix, server, world).max(); return getPermissionRangeValues(permissionPrefix, server, world).max();
} }
/**
* Tells if this entity has the provided permission out of a specific server and world.
* It is either based on the permissions defined directly for this entity, or from inheritance as long as they are
* not overriden.
* @param permission the permission to ckeck on this entity.
* @return true if this entity has the permission, false if it is negated, or null if not known.
*/
public Boolean hasPermission(String permission) { public Boolean hasPermission(String permission) {
return hasPermission(permission, null, null); return hasPermission(permission, null, null);
} }
/**
* Tells if this entity has the provided permission on a specitif server.
* It is either based on the permissions defined directly for this entity, or from inheritance as long as they are
* not overriden. It also consider permissions that apply on any server.
* @param permission the permission to ckeck on this entity.
* @param server the server in which to test the permission for this entity.
* @return true if this entity has the permission, false if it is negated, or null if not known.
*/
public Boolean hasPermission(String permission, String server) { public Boolean hasPermission(String permission, String server) {
return hasPermission(permission, server, null); return hasPermission(permission, server, null);
} }
/**
* Tells if this entity has the provided permission on a specitif server and world.
* It is either based on the permissions defined directly for this entity, or from inheritance as long as they are
* not overriden. It also consider permissions that apply on any world of that server, and then any server.
* @param permission the permission to ckeck on this entity.
* @param server the server in which to test the permission for this entity.
* @param world the world in which to test the permission for this entity.
* @return true if this entity has the permission, false if it is negated, or null if not known.
*/
public Boolean hasPermission(String permission, String server, String world) { public Boolean hasPermission(String permission, String server, String world) {
Boolean ret = Permissions.resolver.getEffectivePermission(name, type, permission, server, world); Boolean ret = Permissions.resolver.getEffectivePermission(name, type, permission, server, world);
Log.debug("[Perm] For " + type.toString().toLowerCase() + " " + getName() + ", '" + permission + "' is " + ret); Log.debug("[Perm] For " + type.toString().toLowerCase() + " " + getName() + ", '" + permission + "' is " + ret);
return ret; return ret;
} }
/**
* Tells if this entity has the provided permission on a specitif server and world.
* It is either based on the permissions defined directly for this entity, or from inheritance as long as they are
* not overriden.
* @param permission the permission to ckeck on this entity.
* @param server the server in which to test the permission for this entity.
* @param world the world in which to test the permission for this entity.
* @param deflt the default value is the permission is undefined for this entity.
* @return true if this entity has the permission, false if it is negated, or {@code deflt} if not known.
*/
public boolean hasPermissionOr(String permission, String server, String world, boolean deflt) { public boolean hasPermissionOr(String permission, String server, String world, boolean deflt) {
Boolean ret = hasPermission(permission, server, world); Boolean ret = hasPermission(permission, server, world);
return ret != null ? ret : deflt; return ret != null ? ret : deflt;
} }
/**
* Evaluates the provided permission expression for this entity.
* It uses {@link #hasPermissionOr(String, String, String, boolean)} with {@code false} as a default value, to check
* each permission nodes individualy.
* @param permExpression the permission expression to evaluate on this entity.
* @param server the server in which to test the permission expression for this entity.
* @param world the world in which to test the permission expression for this entity.
* @return true if this the permission expression evaluates to true, false otherwise.
* @see PermissionExpressionParser#evaluate(String, LitteralPermissionTester)
*/
public boolean hasPermissionExpression(String permExpression, String server, String world) { public boolean hasPermissionExpression(String permExpression, String server, String world) {
return PermissionExpressionParser.evaluate(permExpression, p -> hasPermissionOr(p, server, world, false)); return PermissionExpressionParser.evaluate(permExpression, p -> hasPermissionOr(p, server, world, false));
} }
/**
* Provides informations on how the effective permission of this entity on the provided permission node is
* determined.
* @param permission the permission node to debug on this entity.
* @return a {@link ChatTreeNode} providing informations on how the effective permission is determined.
*/
public ChatTreeNode debugPermission(String permission) { public ChatTreeNode debugPermission(String permission) {
return debugPermission(permission, null, null); return debugPermission(permission, null, null);
} }
/**
* Provides informations on how the effective permission of this entity on the provided permission node is
* determined.
* @param permission the permission node to debug on this entity.
* @param server the server in which to test the permission for this entity.
* @return a {@link ChatTreeNode} providing informations on how the effective permission is determined.
*/
public ChatTreeNode debugPermission(String permission, String server) { public ChatTreeNode debugPermission(String permission, String server) {
return debugPermission(permission, server, null); return debugPermission(permission, server, null);
} }
/**
* Provides informations on how the effective permission of this entity on the provided permission node is
* determined.
* @param permission the permission node to debug on this entity.
* @param server the server in which to test the permission for this entity.
* @param world the world in which to test the permission for this entity.
* @return a {@link ChatTreeNode} providing informations on how the effective permission is determined.
*/
public ChatTreeNode debugPermission(String permission, String server, String world) { public ChatTreeNode debugPermission(String permission, String server, String world) {
return Permissions.resolver.debugPermission(name, type, permission, server, world); return Permissions.resolver.debugPermission(name, type, permission, server, world);
} }
/**
* Adds the provided permission node to this entity that apply on any server.
* @param permission the permission node to add.
*/
public void addSelfPermission(String permission) { public void addSelfPermission(String permission) {
addSelfPermission(permission, null, null); addSelfPermission(permission, null, null);
} }
/**
* Adds the provided permission node to this entity that apply on the provided server.
* @param permission the permission node to add.
* @param server the server in which to apply the permission.
*/
public void addSelfPermission(String permission, String server) { public void addSelfPermission(String permission, String server) {
addSelfPermission(permission, server, null); addSelfPermission(permission, server, null);
} }
/**
* Adds the provided permission node to this entity that apply on the provided server and world.
* @param permission the permission node to add.
* @param server the server in which to apply the permission.
* @param world the world in which to apply the permission.
*/
public void addSelfPermission(String permission, String server, String world) { public void addSelfPermission(String permission, String server, String world) {
Permissions.backendWriter.addSelfPermission(name, type, permission, server, world); Permissions.backendWriter.addSelfPermission(name, type, permission, server, world);
} }
/**
* Removes the provided permission node from this entity that applied on any server.
* @param permission the permission node to add.
*/
public void removeSelfPermission(String permission) { public void removeSelfPermission(String permission) {
removeSelfPermission(permission, null, null); removeSelfPermission(permission, null, null);
} }
/**
* Removes the provided permission node from this entity that applied on the provided server.
* @param permission the permission node to remove.
* @param server the server from which to remove the permission.
*/
public void removeSelfPermission(String permission, String server) { public void removeSelfPermission(String permission, String server) {
removeSelfPermission(permission, server, null); removeSelfPermission(permission, server, null);
} }
/**
* Removes the provided permission node from this entity that applied on the provided server and world.
* @param permission the permission node to remove.
* @param server the server from which to remove the permission.
* @param world the world from which to remove the permission.
*/
public void removeSelfPermission(String permission, String server, String world) { public void removeSelfPermission(String permission, String server, String world) {
Permissions.backendWriter.removeSelfPermission(name, type, permission, server, world); Permissions.backendWriter.removeSelfPermission(name, type, permission, server, world);
} }
/**
* Counts the number of self permission nodes for this entity.
* @return the number of self permission nodes for this entity.
*/
public int getSelfPermissionsCount() { public int getSelfPermissionsCount() {
return getSelfPermissionsServerWorldKeys().stream() return getSelfPermissionsServerWorldKeys().stream()
.mapToInt(key -> getSelfPermissions(key.server, key.world).size()) .mapToInt(key -> getSelfPermissions(key.server(), key.world()).size())
.sum(); .sum();
} }
/**
* Gets all the server/world attribution that have at least one self permission for this entity.
* @return all the server/world attribution that have at least one self permission for this entity.
*/
public Set<ServerWorldKey> getSelfPermissionsServerWorldKeys() { public Set<ServerWorldKey> getSelfPermissionsServerWorldKeys() {
return getBackendEntity().getSelfPermissionsServerWorldKeys(); return getBackendEntity().getSelfPermissionsServerWorldKeys();
} }
/**
* Gets all the self permission nodes that apply everywhere for this entity.
* @return all the self permission nodes that apply everywhere for this entity.
*/
public List<String> getSelfPermissions() { public List<String> getSelfPermissions() {
return getSelfPermissions(null, null); return getSelfPermissions(null, null);
} }
/**
* Gets all the self permission nodes that apply on the provided server for this entity.
* @param server the server from which to get the permissions.
* @return all the self permission nodes that apply on the provided server for this entity.
*/
public List<String> getSelfPermissions(String server) { public List<String> getSelfPermissions(String server) {
return getSelfPermissions(server, null); return getSelfPermissions(server, null);
} }
/**
* Gets all the self permission nodes that apply on the provided server and world for this entity.
* @param server the server from which to get the permissions.
* @param world the world from which to get the permissions.
* @return all the self permission nodes that apply on the provided server and world for this entity.
*/
public List<String> getSelfPermissions(String server, String world) { public List<String> getSelfPermissions(String server, String world) {
return getBackendEntity().getSelfPermissions(server, world); return getBackendEntity().getSelfPermissions(server, world);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return obj instanceof PermEntity o return obj instanceof PermEntity o

View File

@ -6,7 +6,10 @@ import java.util.stream.Collectors;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedGroup; import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedGroup;
import fr.pandacube.lib.permissions.SQLPermissions.EntityType; import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
public class PermGroup extends PermEntity { /**
* Represents an group in the permission system.
*/
public final class PermGroup extends PermEntity {
/* package */ PermGroup(String name) { /* package */ PermGroup(String name) {
super(name, EntityType.Group); super(name, EntityType.Group);
} }
@ -32,26 +35,53 @@ public class PermGroup extends PermEntity {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* Tells if this group is a default group.
* A player inherits all default groups when they dont explicitely inherit from at least one group.
* @return true if this group is a default group, false otherwise.
*/
public boolean isDefault() { public boolean isDefault() {
return getBackendEntity().deflt; return getBackendEntity().deflt;
} }
/**
* Sets this group as a default group or not.
* All players that dont explicitely inherit from at least one group will either start or stop implicitely
* inheriting from this group.
* @param deflt true to set this group as default, false to set is as not default.
*/
public void setDefault(boolean deflt) { public void setDefault(boolean deflt) {
Permissions.backendWriter.setGroupDefault(name, deflt); Permissions.backendWriter.setGroupDefault(name, deflt);
} }
/**
* Makes this group inherit the provided group.
* @param group the name of the group to inherit from.
*/
public void addInheritance(String group) { public void addInheritance(String group) {
Permissions.backendWriter.addInheritance(name, type, group); Permissions.backendWriter.addInheritance(name, type, group);
} }
/**
* Makes this group inherit the provided group.
* @param group the group to inherit from.
*/
public void addInheritance(PermGroup group) { public void addInheritance(PermGroup group) {
addInheritance(group.name); addInheritance(group.name);
} }
/**
* Makes this group stop inheriting from the provided group.
* @param group the name of the group to stop inheriting from.
*/
public void removeInheritance(String group) { public void removeInheritance(String group) {
Permissions.backendWriter.removeInheritance(name, type, group); Permissions.backendWriter.removeInheritance(name, type, group);
} }
/**
* Makes this group stop inheriting from the provided group.
* @param group the group to stop inheriting from.
*/
public void removeInheritance(PermGroup group) { public void removeInheritance(PermGroup group) {
removeInheritance(group.name); removeInheritance(group.name);
} }

View File

@ -7,7 +7,10 @@ import java.util.stream.Collectors;
import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer; import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer;
import fr.pandacube.lib.permissions.SQLPermissions.EntityType; import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
public class PermPlayer extends PermEntity { /**
* Represents an player in the permission system.
*/
public final class PermPlayer extends PermEntity {
private final UUID playerId; private final UUID playerId;
/* package */ PermPlayer(UUID id) { /* package */ PermPlayer(UUID id) {
super(id.toString(), EntityType.User); super(id.toString(), EntityType.User);
@ -30,6 +33,10 @@ public class PermPlayer extends PermEntity {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* Gets the UUID of this player.
* @return the UUID of this player.
*/
public UUID getPlayerId() { public UUID getPlayerId() {
return playerId; return playerId;
} }
@ -48,14 +55,18 @@ public class PermPlayer extends PermEntity {
} }
/** /**
* Gets all the groups this player belongs to.
* Alias for {@link #getInheritances()}. * Alias for {@link #getInheritances()}.
* @return a list of all the groups this player belongs to.
*/ */
public List<PermGroup> getGroups() { public List<PermGroup> getGroups() {
return getInheritances(); return getInheritances();
} }
/** /**
* Gets all the group names this player belongs to.
* Alias for {@link #getInheritances()}. * Alias for {@link #getInheritances()}.
* @return a list of all the group names this player belongs to.
*/ */
public List<String> getGroupsString() { public List<String> getGroupsString() {
return getInheritancesString(); return getInheritancesString();
@ -65,35 +76,67 @@ public class PermPlayer extends PermEntity {
* Tells if the player is directly part of a group. * Tells if the player is directly part of a group.
* This is equivalent to {@link #inheritsFromGroup(String, boolean) inheritsFromGroup(group, false)} * This is equivalent to {@link #inheritsFromGroup(String, boolean) inheritsFromGroup(group, false)}
* @param group the group to search for * @param group the group to search for
* @return true if the player is directly part of a group, false otherwise.
*/ */
public boolean isInGroup(String group) { public boolean isInGroup(String group) {
return inheritsFromGroup(group, false); return inheritsFromGroup(group, false);
} }
/**
* Tells if this player has been assigned to the default groups.
* @return true if this player has been assigned to the default groups, or false if this player belongs explicitely
* to their groups.
*/
public boolean isUsingDefaultGroups() { public boolean isUsingDefaultGroups() {
return getBackendEntity().usingDefaultGroups; return getBackendEntity().usingDefaultGroups;
} }
/**
* Sets the group this player will now inheritate, removing all previously inherited groups.
* To keep the other inherited groups, use {@link #addGroup(String)}.
* @param group the name of the group to inherit from.
*/
public void setGroup(String group) { public void setGroup(String group) {
Permissions.backendWriter.setInheritance(name, type, group); Permissions.backendWriter.setInheritance(name, type, group);
} }
/**
* Sets the group this player will now inheritate, removing all previously inherited groups.
* To keep the other inherited groups, use {@link #addGroup(PermGroup)}.
* @param group the group to inherit from.
*/
public void setGroup(PermGroup group) { public void setGroup(PermGroup group) {
setGroup(group.name); setGroup(group.name);
} }
/**
* Makes this player inherit the provided group, keeping the other groups they already inherits from.
* @param group the name of the group to inherit from.
*/
public void addGroup(String group) { public void addGroup(String group) {
Permissions.backendWriter.addInheritance(name, type, group); Permissions.backendWriter.addInheritance(name, type, group);
} }
/**
* Makes this player inherit the provided group, keeping the other groups they already inherits from.
* @param group the group to inherit from.
*/
public void addGroup(PermGroup group) { public void addGroup(PermGroup group) {
addGroup(group.name); addGroup(group.name);
} }
/**
* Makes this player stop inheriting from the provided group.
* @param group the name of the group to stop inheriting from.
*/
public void removeGroup(String group) { public void removeGroup(String group) {
Permissions.backendWriter.removeInheritance(name, type, group); Permissions.backendWriter.removeInheritance(name, type, group);
} }
/**
* Makes this player stop inheriting from the provided group.
* @param group the group to stop inheriting from.
*/
public void removeGroup(PermGroup group) { public void removeGroup(PermGroup group) {
removeGroup(group.name); removeGroup(group.name);
} }

View File

@ -10,10 +10,38 @@ import com.fathzer.soft.javaluator.Operator;
import com.fathzer.soft.javaluator.Operator.Associativity; import com.fathzer.soft.javaluator.Operator.Associativity;
import com.fathzer.soft.javaluator.Parameters; import com.fathzer.soft.javaluator.Parameters;
/**
* Class that evaluates a permission string as if it was a boolean expression with permission nodes as variables.
* <p>
* A permission expression contains permission nodes, boolean operators ({@code "||"}, {@code "&&"} and {@code "!"}) and
* literal values {@code "true"} and {@code "false"}.
* Here are some example of permission expressions:
* <pre>{@code
* "p1.cmd"
* "!p1.toto"
* "p1.cmd!"
* "p1.cmd || p1.toto"
* "p1.cmd && p1.toto"
* "p1.cmd && !p1.toto "
* "p1.cmd && true"
* "false || p2.cmd"
* }</pre>
* Notice that spaces around permission nodes and operators does not affect the results of the parsing.
*/
public class PermissionExpressionParser { public class PermissionExpressionParser {
private static final PermissionEvaluator PERMISSION_EVALUATOR = new PermissionEvaluator(); private static final PermissionEvaluator PERMISSION_EVALUATOR = new PermissionEvaluator();
/**
* Evaluate the provided permission expression, testing each permission with the provided permTester.
*
* @param permString the permission expression to evaluate.
* @param permTester a function that gives the value of the provided permission node. It is usually a method
* reference to the {@code hasPermission(String)} method the player we want to test the
* permissions.
* @throws IllegalArgumentException if the expression is not correct.
* @return the result of the evaluation of the permission expression.
*/
public static boolean evaluate(String permString, LitteralPermissionTester permTester) { public static boolean evaluate(String permString, LitteralPermissionTester permTester) {
try { try {
return PERMISSION_EVALUATOR.evaluate(permString, permTester); return PERMISSION_EVALUATOR.evaluate(permString, permTester);
@ -22,6 +50,9 @@ public class PermissionExpressionParser {
} }
} }
/**
* Functional interface that converts a string into a boolean.
*/
public interface LitteralPermissionTester extends Function<String, Boolean> { } public interface LitteralPermissionTester extends Function<String, Boolean> { }
@ -89,41 +120,4 @@ public class PermissionExpressionParser {
} }
} }
/* TODO move to test code
public static void main(String[] args) {
java.util.List<String> pList = java.util.Arrays.asList("p1.cmd", "p1.toto", "p2.lol");
LitteralPermissionTester tester = p -> pList.contains(p);
for (String permExpr : java.util.Arrays.asList(
"p1.cmd", // true
"p1.notexist", // false
"p2lol.lol", // false
"!p1.notexist", // true
"!p1.cmd", // false
"p1.cmd!", // false
"p1.cmd! p2.lol", // exception
"p1.cmd || p1.toto", // true || true == true
"p1.cmd || p1.notexist", // true || false == true
"p1.fefef || p2.lol", // false || true == true
"p1.fefef || p2.lolilol", // false || false == false
"p1.cmd && p1.toto", // true && true == true
"p1.cmd && p1.notexist", // true && false == false
"p1.fefef && p2.lol", // false && true == false
"p1.fefef && p2.lolilol", // false && false == false
"p1.cmd && !p1.toto ", // true && !true == false
" !p1.cmd && p1.toto", // !true && true == false
"!p1.cmd & p1.toto", // exception
"!p1.cmd | p1.toto", // exception
"p1.not exist" // exception
)) {
try {
System.out.println(permExpr + " -> " + evaluate(permExpr, tester));
} catch (Exception e) {
e.printStackTrace();
}
}
}
*/
} }

View File

@ -10,6 +10,13 @@ import fr.pandacube.lib.db.DBConnection;
import fr.pandacube.lib.db.DBException; import fr.pandacube.lib.db.DBException;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
/**
* Main class for the Pandalib permission system.
* <p>
* This permission system uses the Pandalib DB API to connect to the database, so the connection to the MySQL must be
* established first, using {@link DB#init(DBConnection, String)}.
* Then, this class must be initialized using {@link #init(Function)}.
*/
public class Permissions { public class Permissions {
/* package */ static PermissionsCachedBackendReader backendReader; /* package */ static PermissionsCachedBackendReader backendReader;
@ -20,6 +27,10 @@ public class Permissions {
/** /**
* Initialize the permission system. * Initialize the permission system.
* The connection to the database needs to be initialized first, using {@link DB#init(DBConnection, String)}. * The connection to the database needs to be initialized first, using {@link DB#init(DBConnection, String)}.
* @param playerNameGetter a function to get the player name associated with a UUID. It is used for
* and to generate {@link PermPlayer#getName()} and for
* {@link PermEntity#debugPermission(String)}.
* @throws DBException if an error occurs when interacting with the database.
*/ */
public static void init(Function<UUID, String> playerNameGetter) throws DBException { public static void init(Function<UUID, String> playerNameGetter) throws DBException {
Permissions.playerNameGetter = playerNameGetter == null ? UUID::toString : playerNameGetter; Permissions.playerNameGetter = playerNameGetter == null ? UUID::toString : playerNameGetter;
@ -38,6 +49,17 @@ public class Permissions {
} }
} }
private static void checkInitialized() {
if (backendReader == null) {
throw new IllegalStateException("Permissions system not initialized. Check the server logs to check if there is an error during the startup, and check if the init() method is called properly.");
}
}
/**
* Adds the provided special permissions to this permission system.
* @param specialPermissions the {@link SpecialPermission}s to add.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static void addSpecialPermissions(SpecialPermission... specialPermissions) { public static void addSpecialPermissions(SpecialPermission... specialPermissions) {
checkInitialized(); checkInitialized();
if (specialPermissions == null) if (specialPermissions == null)
@ -45,18 +67,23 @@ public class Permissions {
resolver.specialPermissions.addAll(Arrays.asList(specialPermissions)); resolver.specialPermissions.addAll(Arrays.asList(specialPermissions));
} }
private static void checkInitialized() { /**
if (backendReader == null) { * Clears the cached data of a specific player.
throw new IllegalStateException("Permissions system not initialized. Check the server logs to check if there is an error during the startup, and check if the init() method is called properly."); * @param playerId the UUID of the player.
} * @throws IllegalStateException if the permission system was not initialized properly.
} */
public static void clearPlayerCache(UUID playerId) { public static void clearPlayerCache(UUID playerId) {
checkInitialized(); checkInitialized();
backendReader.clearPlayerCache(playerId); backendReader.clearPlayerCache(playerId);
resolver.clearPlayerFromCache(playerId); resolver.clearPlayerFromCache(playerId);
} }
/**
* Clears all the cached data (players and groupds) and fetch all the groups data from the database.
* The clearing and fetching of the data is made asynchronously in a new thread.
* @param then the action to perform after the cache has been updated.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static void clearCache(Runnable then) { public static void clearCache(Runnable then) {
checkInitialized(); checkInitialized();
backendReader.clearAndResetCacheAsync(() -> { backendReader.clearAndResetCacheAsync(() -> {
@ -66,12 +93,24 @@ public class Permissions {
}); });
} }
/**
* Gets the permission player object.
* @param playerId the UUID of the player.
* @return the permission player object.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static PermPlayer getPlayer(UUID playerId) { public static PermPlayer getPlayer(UUID playerId) {
checkInitialized(); checkInitialized();
return new PermPlayer(playerId); return new PermPlayer(playerId);
} }
/**
* Asks the permission system to preventively and asynchronoulsy cache the data of the provided player.
* This can be called as soon as possible when a player connects, so the permission data of the player are
* accessible as soon as possible when they are needed.
* @param playerId the UUID of the player.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static void precachePlayerAsync(UUID playerId) { public static void precachePlayerAsync(UUID playerId) {
checkInitialized(); checkInitialized();
Thread t = new Thread(() -> { Thread t = new Thread(() -> {
@ -85,22 +124,44 @@ public class Permissions {
t.start(); t.start();
} }
/**
* Gets the permission group object.
* @param name the name of the group.
* @return the permission group object.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static PermGroup getGroup(String name) { public static PermGroup getGroup(String name) {
checkInitialized(); checkInitialized();
return new PermGroup(name); return new PermGroup(name);
} }
/**
* Gets all the permission group objects.
* @return all the permission group objects.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static List<PermGroup> getGroups() { public static List<PermGroup> getGroups() {
checkInitialized(); checkInitialized();
return PermGroup.fromCachedGroups(backendReader.getGroups()); return PermGroup.fromCachedGroups(backendReader.getGroups());
} }
/**
* Gets all the default permission group objects.
* @return all the default permission group objects.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static List<PermGroup> getDefaultGroups() { public static List<PermGroup> getDefaultGroups() {
checkInitialized(); checkInitialized();
return PermGroup.fromCachedGroups(backendReader.getDefaultGroups()); return PermGroup.fromCachedGroups(backendReader.getDefaultGroups());
} }
/**
* Gets the full permission list.
* @return the full permission list.
* @throws IllegalStateException if the permission system was not initialized properly.
*/
public static List<String> getFullPermissionsList() { public static List<String> getFullPermissionsList() {
checkInitialized();
return backendReader.getFullPermissionsList(); return backendReader.getFullPermissionsList();
} }

View File

@ -25,7 +25,7 @@ import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer;
import fr.pandacube.lib.permissions.SQLPermissions.EntityType; import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
import fr.pandacube.lib.util.Log; import fr.pandacube.lib.util.Log;
public class PermissionsResolver { /* package */ class PermissionsResolver {
private final PermissionsCachedBackendReader backendReader; private final PermissionsCachedBackendReader backendReader;
@ -400,7 +400,7 @@ public class PermissionsResolver {
ParsedSelfPermission specialPerm = null; ParsedSelfPermission specialPerm = null;
for (SpecialPermission spePerm : specialPermissions) { for (SpecialPermission spePerm : specialPermissions) {
if (spePerm.match().match(permission)) { if (spePerm.matcher().match(permission)) {
boolean res = spePerm.tester().test(permP, permission, server, world); boolean res = spePerm.tester().test(permP, permission, server, world);
specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL);
break; break;

View File

@ -3,13 +3,19 @@ package fr.pandacube.lib.permissions;
import fr.pandacube.lib.db.SQLElement; import fr.pandacube.lib.db.SQLElement;
import fr.pandacube.lib.db.SQLField; import fr.pandacube.lib.db.SQLField;
/**
* SQL Table to store the permissions data.
*/
public class SQLPermissions extends SQLElement<SQLPermissions> { public class SQLPermissions extends SQLElement<SQLPermissions> {
/**
* Instanciate a new entry in the table.
*/
public SQLPermissions() { public SQLPermissions() {
super(); super();
} }
public SQLPermissions(int id) { private SQLPermissions(int id) {
super(id); super(id);
} }
@ -18,22 +24,45 @@ public class SQLPermissions extends SQLElement<SQLPermissions> {
return "permissions"; return "permissions";
} }
/** The name of the entity (player id or group name). */
public static final SQLField<SQLPermissions, String> name = field(VARCHAR(64), false); public static final SQLField<SQLPermissions, String> name = field(VARCHAR(64), false);
/** The entity type, based on {@link EntityType}. */
public static final SQLField<SQLPermissions, Integer> type = field(TINYINT, false); public static final SQLField<SQLPermissions, Integer> type = field(TINYINT, false);
/** The key of the data ("permission", "inheritance", …). */
public static final SQLField<SQLPermissions, String> key = field(VARCHAR(256), false); public static final SQLField<SQLPermissions, String> key = field(VARCHAR(256), false);
/** The data value (permission node, name of inherited group, prefix/suffix, …). */
public static final SQLField<SQLPermissions, String> value = field(VARCHAR(256), false); public static final SQLField<SQLPermissions, String> value = field(VARCHAR(256), false);
/** The server in which the permission apply. */
public static final SQLField<SQLPermissions, String> server = field(VARCHAR(64), true); public static final SQLField<SQLPermissions, String> server = field(VARCHAR(64), true);
/** The world in which the permission apply. */
public static final SQLField<SQLPermissions, String> world = field(VARCHAR(64), true); public static final SQLField<SQLPermissions, String> world = field(VARCHAR(64), true);
/**
* All possible type of entity type.
*/
public enum EntityType { public enum EntityType {
/**
* User entity type.
*/
User, User,
/**
* Group entity type.
*/
Group; Group;
/**
* Returns the database value of this entity type.
* @return the database value of this entity type.
*/
public int getCode() { public int getCode() {
return ordinal(); return ordinal();
} }
/**
* Gets the {@link EntityType} corresponding to the database value.
* @param code the database value.
* @return the {@link EntityType} corresponding to the database value.
*/
public static EntityType getByCode(int code) { public static EntityType getByCode(int code) {
if (code >= 0 && code < values().length) if (code >= 0 && code < values().length)
return values()[code]; return values()[code];

View File

@ -3,11 +3,14 @@ package fr.pandacube.lib.permissions;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
public class ServerWorldKey implements Comparable<ServerWorldKey> { /**
public final String server, world; * A pair of string representing a server and world name, used to organize and filter the permission data of a player or
ServerWorldKey(String s, String w) { * group.
server = s; world = w; * @param server the server name, can be null.
} * @param world the world name, can be null.
*/
public record ServerWorldKey(String server, String world) implements Comparable<ServerWorldKey> {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
return obj instanceof ServerWorldKey o return obj instanceof ServerWorldKey o

View File

@ -2,14 +2,35 @@ package fr.pandacube.lib.permissions;
/** /**
* Represents a permission node that is based on an arbitrary player state. * Represents a permission node that is based on an arbitrary player state.
* @param matcher predicate that tell if the provided permission is our special permission.
* @param tester predicate that tell the value of this special permission, based on the parameters.
*/ */
public record SpecialPermission(PermissionMatcher match, PermissionTester tester) { public record SpecialPermission(PermissionMatcher matcher, PermissionTester tester) {
/**
* Predicate that tell if the provided permission is our special permission.
*/
public interface PermissionMatcher { public interface PermissionMatcher {
/**
* Tells if the provided permission is our special permission.
* @param permission the permission to test.
* @return true if the provided permission is our special permission, false otherwise.
*/
boolean match(String permission); boolean match(String permission);
} }
/**
* Predicate that tell the value of this special permission, based on the parameters.
*/
public interface PermissionTester { public interface PermissionTester {
/**
* Tells the value of this special permission, based on the parameters.
* @param player the player to test the permission on.
* @param permission the permission to test.
* @param server the server on which the player is.
* @param world the world in which the player is.
* @return the value of this special permission, based on the parameters.
*/
boolean test(PermPlayer player, String permission, String server, String world); boolean test(PermPlayer player, String permission, String server, String world);
} }

View File

@ -0,0 +1,109 @@
package fr.pandacube.lib.permissions;
import fr.pandacube.lib.permissions.PermissionExpressionParser.LitteralPermissionTester;
import org.junit.Before;
import org.junit.Test;
import static fr.pandacube.lib.permissions.PermissionExpressionParser.evaluate;
import static org.junit.Assert.*;
public class PermissionExpressionParserTest {
java.util.List<String> pList = java.util.Arrays.asList("p1.cmd", "p1.toto", "p2.lol");
LitteralPermissionTester tester = p -> pList.contains(p);
@Test
public void evaluateTrue() {
assertTrue(evaluate("p1.cmd", tester));
}
@Test
public void evaluateFalse() {
assertFalse(evaluate("p1.notexist", tester));
}
@Test
public void evaluateNegateFalse() {
assertTrue(evaluate("!p1.notexist", tester));
}
@Test
public void evaluateNegateTrue() {
assertFalse(evaluate("!p1.cmd", tester));
}
@Test
public void evaluateRevNegateTrue() {
assertFalse(evaluate("p1.cmd!", tester));
}
@Test
public void evaluateOrBothTrue() {
assertTrue(evaluate("p1.cmd || p1.toto", tester));
}
@Test
public void evaluateOrTrueFalse() {
assertTrue(evaluate("p1.cmd || p1.notexist", tester));
}
@Test
public void evaluateOrFalseTrue() {
assertTrue(evaluate("p1.fefef || p2.lol", tester));
}
@Test
public void evaluateOrBothFalse() {
assertFalse(evaluate("p1.fefef || p2.lolilol", tester));
}
@Test
public void evaluateAndBothTrue() {
assertTrue(evaluate("p1.cmd && p1.toto", tester));
}
@Test
public void evaluateAndTrueFalse() {
assertFalse(evaluate("p1.cmd && p1.notexist", tester));
}
@Test
public void evaluateAndFalseTrue() {
assertFalse(evaluate("p1.fefef && p2.lol", tester));
}
@Test
public void evaluateAndBothFalse() {
assertFalse(evaluate("p1.fefef && p2.lolilol", tester));
}
@Test
public void evaluateAndTrueNegateTrueWithSomeExtraSpaces() {
assertFalse(evaluate("p1.cmd && !p1.toto ", tester));
}
@Test
public void evaluateAndNegateTrueTrueWithLotOfExtraSpaces() {
assertFalse(evaluate(" !p1.cmd && p1.toto ", tester));
}
@Test(expected = IllegalArgumentException.class)
public void evaluateBadSyntax1() {
evaluate("p1.cmd! p2.lol", tester);
}
@Test(expected = IllegalArgumentException.class)
public void evaluateBadSyntax2() {
evaluate("!p1.cmd & p1.toto", tester);
}
@Test(expected = IllegalArgumentException.class)
public void evaluateBadSyntax3() {
evaluate("!p1.cmd | p1.toto", tester);
}
@Test(expected = IllegalArgumentException.class)
public void evaluateBadSyntax4() {
evaluate("p1.not exist", tester);
}
}

View File

@ -6,9 +6,12 @@ import java.util.stream.LongStream;
import fr.pandacube.lib.chat.ChatColorUtil; import fr.pandacube.lib.chat.ChatColorUtil;
import fr.pandacube.lib.permissions.PermPlayer; import fr.pandacube.lib.permissions.PermPlayer;
import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.permissions.Permissions;
import fr.pandacube.lib.players.standalone.StandaloneOffPlayer; import fr.pandacube.lib.players.standalone.AbstractOffPlayer;
public interface PermissibleOffPlayer extends StandaloneOffPlayer { /**
* Represents a player, either offline or online, with extra methods related to the {@code pandalib-permissions} system.
*/
public interface PermissibleOffPlayer extends AbstractOffPlayer {
@ -44,6 +47,7 @@ public interface PermissibleOffPlayer extends StandaloneOffPlayer {
* Get an updated display name of the user, * Get an updated display name of the user,
* generated using eventual permissions prefix(es) and suffix(es) of the player, * generated using eventual permissions prefix(es) and suffix(es) of the player,
* and with color codes translated to Minecrafts native {@code §}. * and with color codes translated to Minecrafts native {@code §}.
* @return the display name of this player, generated by the permission system.
*/ */
default String getDisplayNameFromPermissionSystem() { default String getDisplayNameFromPermissionSystem() {
PermPlayer permU = getPermissionUser(); PermPlayer permU = getPermissionUser();
@ -127,6 +131,8 @@ public interface PermissibleOffPlayer extends StandaloneOffPlayer {
/** /**
* Returns the maximum value returned by {@link PermissibleOffPlayer#getPermissionRangeValues(String)}. * Returns the maximum value returned by {@link PermissibleOffPlayer#getPermissionRangeValues(String)}.
* @param permissionPrefix the permission prefix to search for.
* @return the maximum value for the provided permission range.
*/ */
default OptionalLong getPermissionRangeMax(String permissionPrefix) { default OptionalLong getPermissionRangeMax(String permissionPrefix) {
PermissibleOnlinePlayer online = getOnlineInstance(); PermissibleOnlinePlayer online = getOnlineInstance();
@ -139,8 +145,7 @@ public interface PermissibleOffPlayer extends StandaloneOffPlayer {
} }
/** /**
* Tells if the this player is part of the specified group * Tells if the this player is part of the specified group.
*
* @param group the permissions group * @param group the permissions group
* @return <i>true</i> if this player is part of the group, * @return <i>true</i> if this player is part of the group,
* <i>false</i> otherwise * <i>false</i> otherwise

View File

@ -4,9 +4,12 @@ import java.util.OptionalLong;
import java.util.stream.LongStream; import java.util.stream.LongStream;
import fr.pandacube.lib.permissions.PermissionExpressionParser; import fr.pandacube.lib.permissions.PermissionExpressionParser;
import fr.pandacube.lib.players.standalone.StandaloneOnlinePlayer; import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
public interface PermissibleOnlinePlayer extends PermissibleOffPlayer, StandaloneOnlinePlayer { /**
* Represents an online player, with extra methods related to the {@code pandalib-permissions} system.
*/
public interface PermissibleOnlinePlayer extends PermissibleOffPlayer, AbstractOnlinePlayer {
@ -16,7 +19,7 @@ public interface PermissibleOnlinePlayer extends PermissibleOffPlayer, Standalon
*/ */
/** /**
* @return The current name of this player * {@inheritDoc}
* @implSpec The implementation is expected to call the environment API * @implSpec The implementation is expected to call the environment API
* (Bukkit/Bungee) to get the name of the player. * (Bukkit/Bungee) to get the name of the player.
*/ */

View File

@ -1,140 +0,0 @@
package fr.pandacube.lib.players.permissible;
import java.util.Objects;
import java.util.UUID;
import net.kyori.adventure.text.ComponentLike;
import fr.pandacube.lib.chat.ChatStatic;
import fr.pandacube.lib.players.standalone.StandalonePlayerManager;
public abstract class PermissiblePlayerManager<OP extends PermissibleOnlinePlayer, OF extends PermissibleOffPlayer> extends StandalonePlayerManager<OP, OF> {
private static PermissiblePlayerManager<?, ?> instance;
public static synchronized PermissiblePlayerManager<?, ?> getInstance() {
return instance;
}
private static synchronized void setInstance(PermissiblePlayerManager<?, ?> newInstance) {
if (instance != null) {
throw new IllegalStateException("cannot have multiple instance of PlayerManager");
}
instance = newInstance;
}
public PermissiblePlayerManager() {
super();
setInstance(this);
}
@Override
public void broadcastMessage(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
Objects.requireNonNull(message, "message cannot be null");
if (prefix)
message = ChatStatic.prefixedAndColored(message.asComponent());
for (PermissibleOnlinePlayer op : getAll()) {
if (permission != null && !(op.hasPermission(permission))) continue;
if (sourcePlayer != null)
op.sendMessage(message, sourcePlayer); // CHAT message with UUID
else
op.sendMessage(message); // SYSTEM message
}
if (console)
sendMessageToConsole(message.asComponent());
}
/*
* Message broadcasting
*/
// ComponentLike message
// boolean prefix
// boolean console = (permission == null)
// String permission = null
// UUID sourcePlayer = null
/**
* Broadcast a message to some or all players, and eventually to the console.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param permission if not null, the message is only sent to player with this permission.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
*
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
getInstance().broadcastMessage(message, prefix, console, permission, sourcePlayer);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param permission if not null, the message is only sent to player with this permission.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission) {
broadcast(message, prefix, console, permission, null);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, String, UUID)}.
* <p>
* This method decides to send the message to the console depending on whether {@code permission}
* is null (will send to console) or not (will not send to console). To specify this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, String)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param permission if not null, the message is only sent to player with this permission (but not to console).
* If null, the message will be sent to all players and to console.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, String permission) {
broadcast(message, prefix, (permission == null), permission, null);
}
/**
* Broadcast a message to all players, and to the console.
* <p>
* This method sends the message to the console. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param permission if not null, the message is only sent to player with this permission (but not to console).
* If null, the message will be sent to all players and to console.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, String permission, UUID sourcePlayer) {
broadcast(message, prefix, true, permission, sourcePlayer);
}
}

View File

@ -2,7 +2,10 @@ package fr.pandacube.lib.players.standalone;
import java.util.UUID; import java.util.UUID;
public interface StandaloneOffPlayer { /**
* Represents any player, either offline or online.
*/
public interface AbstractOffPlayer {
@ -12,20 +15,20 @@ public interface StandaloneOffPlayer {
*/ */
/** /**
* Return the ID of the minecraft account. * Returns the {@link UUID} of this player.
*
* @return the id of the player * @return the id of the player
*/ */
UUID getUniqueId(); UUID getUniqueId();
/** /**
* @return the last known player name of this player, or null if this player never joined the network. * The last known player name of this player, or null if it is not known.
* @return the last known player name of this player, or null if it is not known.
*/ */
String getName(); String getName();
/** /**
* Indicate if this player is connected to the current node (server or proxy, depending on interface implementation) * Indicate if this player is connected to the current node (server or proxy, depending on interface implementation).
* @return wether the player is online or not * @return wether the player is online or not.
*/ */
boolean isOnline(); boolean isOnline();
@ -42,8 +45,9 @@ public interface StandaloneOffPlayer {
/** /**
* Return the online instance of this player, if any exists. * Return the online instance of this player, if any exists.
* May return itself if the current instance already represent an online player. * May return itself if the current instance already represent an online player.
* @return the online instance for this player.
*/ */
StandaloneOnlinePlayer getOnlineInstance(); AbstractOnlinePlayer getOnlineInstance();
@ -55,9 +59,9 @@ public interface StandaloneOffPlayer {
*/ */
/** /**
* Returns the name of the player (if any), with eventual prefix and suffix depending on permission groups * Returns the display name of the player (if any), with eventual prefix and suffix depending on permission groups
* (and team for bukkit implementation) * (and team for bukkit implementation).
* @return the display name of the player * @return the display name of the player.
*/ */
String getDisplayName(); String getDisplayName();

View File

@ -9,7 +9,10 @@ import net.kyori.adventure.text.ComponentLike;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { /**
* Represents any online player.
*/
public interface AbstractOnlinePlayer extends AbstractOffPlayer {
@ -19,13 +22,17 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
*/ */
/** /**
* @return The current name of this player * Returns the name of the current server the player is in.
* @apiNote The implementation is expected to call the environment API * The returned value is used by the 'pandalib-permissions' system.
* (Bukkit/Bungee) to get the name of the player. * @return the name of the current server the player is in.
*/ */
String getServerName(); String getServerName();
/**
* Returns the name of the current world the player is in.
* The returned value is used by the 'pandalib-permissions' system.
* @return the name of the current world the player is in.
*/
String getWorldName(); String getWorldName();
@ -40,6 +47,8 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
/** /**
* Tells if this online player has the specified permission. * Tells if this online player has the specified permission.
* @param permission the permission to test on that player.
* @return weither this player has the specified permission or not.
* @implSpec Implementation of this method should call the permission system of their environment (paper/bungee), * @implSpec Implementation of this method should call the permission system of their environment (paper/bungee),
* so this method will work independently of the usage of the 'pandalib-permissions' module. * so this method will work independently of the usage of the 'pandalib-permissions' module.
*/ */
@ -58,15 +67,13 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
*/ */
/** /**
* Display the provided message in the players chat, if * Display the provided message in the players chat, if the chat is activated.
* the chat is activated.
* @param message the message to display. * @param message the message to display.
*/ */
void sendMessage(Component message); void sendMessage(Component message);
/** /**
* Display the provided message in the players chat, if * Display the provided message in the players chat, if the chat is activated.
* the chat is activated.
* @param message the message to display. * @param message the message to display.
*/ */
default void sendMessage(ComponentLike message) { default void sendMessage(ComponentLike message) {
@ -74,8 +81,7 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
} }
/** /**
* Display the provided message in the players chat, if * Display the provided message in the players chat, if they allows to display CHAT messages.
* they allows to display CHAT messages
* @param message the message to display. * @param message the message to display.
* @param sender the player causing the send of this message. Client side filtering may occur. * @param sender the player causing the send of this message. Client side filtering may occur.
* May be null if we dont want client filtering, but still consider the message as CHAT message. * May be null if we dont want client filtering, but still consider the message as CHAT message.
@ -86,8 +92,7 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
void sendMessage(Component message, Identified sender); void sendMessage(Component message, Identified sender);
/** /**
* Display the provided message in the players chat, if * Display the provided message in the players chat, if they allows to display CHAT messages.
* they allows to display CHAT messages
* @param message the message to display * @param message the message to display
* @param sender the player causing the send of this message. Client side filtering may occur. * @param sender the player causing the send of this message. Client side filtering may occur.
* May be null if we dont want client filtering, but still consider the message as CHAT message. * May be null if we dont want client filtering, but still consider the message as CHAT message.
@ -100,8 +105,7 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
} }
/** /**
* Display the provided message in the players chat, if the chat is * Display the provided message in the players chat, if the chat is activated, prepended with the server prefix.
* activated, prepended with the server prefix.
* @param message the message to display * @param message the message to display
*/ */
default void sendPrefixedMessage(ComponentLike message) { default void sendPrefixedMessage(ComponentLike message) {
@ -131,9 +135,8 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
} }
/** /**
* Update the server brand field in the debug menu (F3) of the player * Update the server brand field in the debug menu (F3) of the player (third line in 1.15 debug screen).
* (third line in 1.15 debug screen). Supports ChatColor codes but no * Supports legacy section format but no line break.
* line break.
* @param brand the server brand to send to the client. * @param brand the server brand to send to the client.
*/ */
void sendServerBrand(String brand); void sendServerBrand(String brand);
@ -150,45 +153,55 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
*/ */
/**
* Gets the current client options of this player.
* @return the current client options of this player.
*/
ClientOptions getClientOptions(); ClientOptions getClientOptions();
/**
* Interface providing various configuration values of the Minecraft client.
*/
interface ClientOptions { interface ClientOptions {
/**
* The language of the client interface.
* @return language of the client interface.
*/
Locale getLocale(); Locale getLocale();
/**
* The client view distance, in chunks.
* @return client view distance, in chunks.
*/
int getViewDistance(); int getViewDistance();
/**
* If the chat displays the text colors.
* @return true if the chat displays the text colors, false otherwise.
*/
boolean hasChatColorEnabled(); boolean hasChatColorEnabled();
/** /**
* Tells if the client is configured to completely hide the chat to the * Tells if the client is configured to completely hide the chat to the player. When this is the case, nothing
* player. When this is the case, nothing is displayed in the chat box, * is displayed in the chat box, and the player cant send any message or command.
* and the player cant send any message or command. * @return true if the chat is fully hidden, false otherwise.
* @implSpec if the value is unknown, it is assumed that the chat is
* fully visible.
*/ */
boolean isChatHidden(); boolean isChatHidden();
/** /**
* Tells if the client is configured to display the chat normally. * Tells if the client is configured to display the chat normally. When this is the case, chat messages and
* When this is the case, chat messages and system messages are * system messages are displayed in the chat box, and the player can send messages and commands.
* displayed in the chat box, and the player can send messages and * @return true if the chat is fully visible, false otherwise.
* commands.
* @implSpec if the value is unknown, it is assumed that the chat is
* fully visible.
*/ */
boolean isChatFullyVisible(); boolean isChatFullyVisible();
/** /**
* Tells if the client is configured to only display system messages * Tells if the client is configured to only display system messages in the chat.
* in the chat. * When this is the case, chat messages are hidden but system messages are visible in the chat box, and the
* When this is the case, chat messages are hidden but system messages * player can only send commands.
* are visible in the chat box, and the player can only send commands. * @return true if the chat is visible but only shows system messages, false otherwise.
* @implSpec if the value is unknown, it is assumed that the chat is
* fully visible.
*/ */
boolean isChatOnlyDisplayingSystemMessages(); boolean isChatOnlyDisplayingSystemMessages();
@ -196,57 +209,85 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer {
/** /**
* Tells if the client has configured the main hand on the left. * Tells if the client has configured the main hand on the left.
* @implSpec if the value is unknown, it is assumed that the main hand * @return true if the players character is left handed, false otherwise.
* is on the right.
*/ */
boolean isLeftHanded(); boolean isLeftHanded();
/** /**
* Tells if the client has configured the main hand on the right. * Tells if the client has configured the main hand on the right.
* @implSpec if the value is unknown, it is assumed that the main hand * @return true if the players character is right handed, false otherwise.
* is on the right.
*/ */
boolean isRightHanded(); boolean isRightHanded();
/** /**
* Tells if the client has enabled the filtering of texts on sign and book titles. * Tells if the client has enabled the filtering of texts on sign and book titles.
* Always false as of MC 1.18. * @return true if the client filters swearing in texts, false otherwise.
*/ */
boolean isTextFilteringEnabled(); boolean isTextFilteringEnabled();
/** /**
* Tells if the client allows the server to list their player name in the * Tells if the client allows the server to list their player name in the multiplayer menu.
* multiplayer menu. * <b>To respect the player privacys configuration, this configuration value must be verified when generating
* custom ping response packet (MOTD in multiplayer servers menu) that includes player names.</b>
* @return true if the client allows the server to list their player name in the multiplayer menu, false
* otherwise.
*/ */
boolean allowsServerListing(); boolean allowsServerListing();
/**
* Tells if the cape is enabled on the players skin.
* @return true if the cape is enabled on the players skin, false otherwise.
*/
boolean hasSkinCapeEnabled(); boolean hasSkinCapeEnabled();
/**
* Tells if the jacket is enabled on the players skin.
* @return true if the jacket is enabled on the players skin, false otherwise.
*/
boolean hasSkinJacketEnabled(); boolean hasSkinJacketEnabled();
/**
* Tells if the left sleeve is enabled on the players skin.
* @return true if the left sleeve is enabled on the players skin, false otherwise.
*/
boolean hasSkinLeftSleeveEnabled(); boolean hasSkinLeftSleeveEnabled();
/**
* Tells if the right sleeve is enabled on the players skin.
* @return true if the right sleeve is enabled on the players skin, false otherwise.
*/
boolean hasSkinRightSleeveEnabled(); boolean hasSkinRightSleeveEnabled();
/**
* Tells if the left pants is enabled on the players skin.
* @return true if the left pants is enabled on the players skin, false otherwise.
*/
boolean hasSkinLeftPantsEnabled(); boolean hasSkinLeftPantsEnabled();
/**
* Tells if the right pants is enabled on the players skin.
* @return true if the right pants is enabled on the players skin, false otherwise.
*/
boolean hasSkinRightPantsEnabled(); boolean hasSkinRightPantsEnabled();
/**
* Tells if the hat is enabled on the players skin.
* @return true if the hat is enabled on the players skin, false otherwise.
*/
boolean hasSkinHatsEnabled(); boolean hasSkinHatsEnabled();
} }
/** /**
* Tells if the player can send chat messages or receive chat messages from * Tells if the player can send chat messages or receive chat messages from other players, according to their client
* other players, according to their client configuration. * configuration.
* <br> * <p>
* Chat messages represent public communication between players. By default, * Chat messages represent public communication between players. By default, it only include actual chat message.
* it only include actual chat message. This method may be used in commands * This method may be used in commands like /me, /afk or the login/logout broadcasted messages.
* like /me, /afk or the login/logout broadcasted messages * @return true if the player can send chat messages or receive chat messages from other players, false otherwise.
*/ */
default boolean canChat() { default boolean canChat() {
return getClientOptions().isChatFullyVisible(); return getClientOptions().isChatFullyVisible();

View File

@ -0,0 +1,372 @@
package fr.pandacube.lib.players.standalone;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import fr.pandacube.lib.chat.ChatStatic;
/**
* Generic implementation of a player manager, handling instances of {@link AbstractOffPlayer} and
* {@link AbstractOnlinePlayer}.
* Subclasses should handle the addition and removal of {@link AbstractOnlinePlayer} instances according to the
* currently online players.
* @param <OP> the type of subclass of {@link AbstractOnlinePlayer}
* @param <OF> the type of subclass of {@link AbstractOffPlayer}
*/
public abstract class AbstractPlayerManager<OP extends AbstractOnlinePlayer, OF extends AbstractOffPlayer> {
private static AbstractPlayerManager<?, ?> instance;
/**
* Gets the current instance of player manager.
* @return the player manager.
*/
public static synchronized AbstractPlayerManager<?, ?> getInstance() {
return instance;
}
private static synchronized void setInstance(AbstractPlayerManager<?, ?> newInstance) {
if (instance != null) {
throw new IllegalStateException("cannot have multiple instance of PlayerManager");
}
instance = newInstance;
}
private final Map<UUID, OP> onlinePlayers = Collections.synchronizedMap(new HashMap<>());
private final LoadingCache<UUID, OF> offlinePlayers = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(CacheLoader.from(this::newOffPlayerInstance));
/**
* Creates a new instance of player manager.
*/
public AbstractPlayerManager() {
setInstance(this);
}
/**
* Adds the provided player to this player manager.
* Usually this is called by a player join event handler.
* @param p the player to add.
*/
protected void addPlayer(OP p) {
onlinePlayers.put(p.getUniqueId(), p);
offlinePlayers.invalidate(p.getUniqueId());
}
/**
* Removes the player from this player manager.
* Usually this is called by a player quit event handler.
* @param p the UUID of the player to remove.
* @return the player that has been removed.
*/
protected OP removePlayer(UUID p) {
return onlinePlayers.remove(p);
}
/**
* Get the online player that has the provided UUID.
* @param p the UUID of the player.
* @return the online player instance.
*/
public OP get(UUID p) {
return onlinePlayers.get(p);
}
/**
* Tells if the provided player is online.
* @param p the UUID of the player.
* @return true if the provided player is online (that is when this player manager contains an online player
* instance), false otherwise.
*/
public boolean isOnline(UUID p) {
return onlinePlayers.containsKey(p);
}
/**
* Get the number of online player registered in this player manager.
* @return the number of online player registered in this player manager.
*/
public int getPlayerCount() {
return onlinePlayers.size();
}
/**
* Get all the online players is this player manager.
* @return all the online players is this player manager.
*/
public List<OP> getAll() {
return new ArrayList<>(onlinePlayers.values());
}
/**
* Returns an instance of {@link AbstractOffPlayer} corresponding to a player with the provided {@link UUID}.
* @param p the UUID of the player.
* @return an instance of {@link AbstractOffPlayer}. It can be a new instance, an {@link AbstractOnlinePlayer}
* instance if the player is online, or a cached instance of {@link AbstractOffPlayer}.
*/
public OF getOffline(UUID p) {
if (p == null)
return null;
OP online = get(p);
if (online != null) {
offlinePlayers.invalidate(p);
@SuppressWarnings("unchecked")
OF ret = (OF) online;
return ret;
}
// if not online
try {
return offlinePlayers.get(p); // load and cache new instance if necessary
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e.getCause());
}
}
/**
* Create a new instance of the appropriate subclass of {@link AbstractOffPlayer}.
* @param p the uuid of the player.
* @return the new instance of {@link AbstractOffPlayer}.
*/
protected abstract OF newOffPlayerInstance(UUID p);
/**
* Abstract method to implement to send a message to the console.
* @param message the message to send to the console.
*/
protected abstract void sendMessageToConsole(Component message);
/**
* Broadcast a message to some or all players, and eventually to the console.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param permission if not null, the message is only sent to player with this permission.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
* @throws IllegalArgumentException if message is null.
* @implSpec subclasses may override this method, for instance to restrict the players being able to see the message
* (like /ignored players).
*/
public void broadcastMessage(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
Objects.requireNonNull(message, "message cannot be null");
if (prefix)
message = ChatStatic.prefixedAndColored(message.asComponent());
for (OP op : getAll()) {
if (permission != null && !(op.hasPermission(permission))) continue;
if (sourcePlayer != null)
op.sendMessage(message, sourcePlayer); // CHAT message with UUID
else
op.sendMessage(message); // SYSTEM message
}
if (console)
sendMessageToConsole(message.asComponent());
}
/*
* Message broadcasting
*/
/**
* Broadcast a message to some or all players, and eventually to the console.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param permission if not null, the message is only sent to player with this permission.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
*
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
getInstance().broadcastMessage(message, prefix, console, permission, sourcePlayer);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param permission if not null, the message is only sent to player with this permission.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console, String permission) {
broadcast(message, prefix, console, permission, null);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, String, UUID)}.
* <p>
* This method decides to send the message to the console depending on whether {@code permission}
* is null (will send to console) or not (will not send to console). To specify this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, String)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param permission if not null, the message is only sent to player with this permission (but not to console).
* If null, the message will be sent to all players and to console.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, String permission) {
broadcast(message, prefix, (permission == null), permission, null);
}
/**
* Broadcast a message to all players, and to the console.
* <p>
* This method sends the message to the console. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param permission if not null, the message is only sent to player with this permission (but not to console).
* If null, the message will be sent to all players and to console.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, String permission, UUID sourcePlayer) {
broadcast(message, prefix, true, permission, sourcePlayer);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method doesnt restrict to a specific permission. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, String, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
*
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console, UUID sourcePlayer) {
getInstance().broadcastMessage(message, prefix, console, null, sourcePlayer);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
* <p>
* This method doesnt restrict to a specific permission. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, String)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console) {
broadcast(message, prefix, console, null, null);
}
/**
* Broadcast a message to all players, and to the console.
* <p>
* This method sends the message to the console. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
* <p>
* This method doesnt restrict to a specific permission. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, String, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, UUID sourcePlayer) {
broadcast(message, prefix, true, null, sourcePlayer);
}
/**
* Broadcast a message to all players, and to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, UUID)}.
* <p>
* This method sends the message to the console. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean)}.
* <p>
* This method doesnt restrict to a specific permission. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, String)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix) {
broadcast(message, prefix, true, null, null);
}
}

View File

@ -1,216 +0,0 @@
package fr.pandacube.lib.players.standalone;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import fr.pandacube.lib.chat.ChatStatic;
public abstract class StandalonePlayerManager<OP extends StandaloneOnlinePlayer, OF extends StandaloneOffPlayer> {
private static StandalonePlayerManager<?, ?> instance;
public static synchronized StandalonePlayerManager<?, ?> getInstance() {
return instance;
}
private static synchronized void setInstance(StandalonePlayerManager<?, ?> newInstance) {
if (instance != null) {
throw new IllegalStateException("cannot have multiple instance of PlayerManager");
}
instance = newInstance;
}
private final Map<UUID, OP> onlinePlayers = Collections.synchronizedMap(new HashMap<>());
private final LoadingCache<UUID, OF> offlinePlayers = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(CacheLoader.from(this::newOffPlayerInstance));
public StandalonePlayerManager() {
setInstance(this);
}
protected void addPlayer(OP p) {
onlinePlayers.put(p.getUniqueId(), p);
offlinePlayers.invalidate(p.getUniqueId());
}
protected OP removePlayer(UUID p) {
return onlinePlayers.remove(p);
}
public OP get(UUID p) {
return onlinePlayers.get(p);
}
public boolean isOnline(UUID p) {
return onlinePlayers.containsKey(p);
}
public int getPlayerCount() {
return onlinePlayers.size();
}
public List<OP> getAll() {
return new ArrayList<>(onlinePlayers.values());
}
public OF getOffline(UUID p) {
if (p == null)
return null;
OP online = get(p);
if (online != null) {
offlinePlayers.invalidate(p);
@SuppressWarnings("unchecked")
OF ret = (OF) online;
return ret;
}
// if not online
try {
return offlinePlayers.get(p); // load and cache new instance if necessary
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e.getCause());
}
}
protected abstract OF newOffPlayerInstance(UUID p);
protected abstract void sendMessageToConsole(Component message);
public void broadcastMessage(ComponentLike message, boolean prefix, boolean console, String permission, UUID sourcePlayer) {
Objects.requireNonNull(message, "message cannot be null");
if (prefix)
message = ChatStatic.prefixedAndColored(message.asComponent());
for (StandaloneOnlinePlayer op : getAll()) {
if (sourcePlayer != null)
op.sendMessage(message, sourcePlayer); // CHAT message without UUID
else
op.sendMessage(message); // SYSTEM message
}
if (console)
getInstance().sendMessageToConsole(message.asComponent());
}
/*
* Message broadcasting
*/
// ComponentLike message
// boolean prefix
// boolean console = (permission == null)
// UUID sourcePlayer = null
/**
* Broadcast a message to some or all players, and eventually to the console.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
*
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console, UUID sourcePlayer) {
getInstance().broadcastMessage(message, prefix, console, null, sourcePlayer);
}
/**
* Broadcast a message to some or all players, and eventually to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param console if the message must be displayed in the console.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, boolean console) {
broadcast(message, prefix, console, null);
}
/**
* Broadcast a message to all players, and to the console.
* <p>
* This method sends the message to the console. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean, UUID)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @param sourcePlayer specifiy the eventual player that is the source of the message.
* If null, the message will be sent as a SYSTEM chat message.
* If not null, the message will be sent as a CHAT message, and will not be sent
* to players ignoring the provided player (if implemented).
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix, UUID sourcePlayer) {
broadcast(message, prefix, true, sourcePlayer);
}
/**
* Broadcast a message to all players, and to the console.
* <p>
* This method assumes this message is not caused by a specific player. To specify the source player, use
* {@link #broadcast(ComponentLike, boolean, UUID)}.
* <p>
* This method sends the message to the console. To change this behaviour, use
* {@link #broadcast(ComponentLike, boolean, boolean)}.
*
* @param message the message to send.
* @param prefix if the server prefix will be prepended to the message.
* @throws IllegalArgumentException if message is null.
*/
public static void broadcast(ComponentLike message, boolean prefix) {
broadcast(message, prefix, true, null);
}
}

View File

@ -12,5 +12,9 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface ConcreteWrapper { public @interface ConcreteWrapper {
/**
* The class representing a dummy implementation of the annotated wrapper interface.
* @return the class representing a dummy implementation of the annotated wrapper interface.
*/
Class<? extends ReflectWrapper> value(); Class<? extends ReflectWrapper> value();
} }

View File

@ -6,6 +6,11 @@ import java.util.function.Supplier;
import fr.pandacube.lib.util.MappedListView; import fr.pandacube.lib.util.MappedListView;
/**
* A wrapper for a list of wrapped object. It is an extension of {@link MappedListView} that is used to transparently
* wrap/unwrap the elements of the backend list.
* @param <W> the type of the reflect wrapper for the elements of this list.
*/
public class ReflectListWrapper<W extends ReflectWrapperI> extends MappedListView<Object, W> implements ReflectWrapperTypedI<List<Object>> { public class ReflectListWrapper<W extends ReflectWrapperI> extends MappedListView<Object, W> implements ReflectWrapperTypedI<List<Object>> {
private final Class<W> expectedWrapperClass; private final Class<W> expectedWrapperClass;
@ -14,11 +19,12 @@ public class ReflectListWrapper<W extends ReflectWrapperI> extends MappedListVie
this(ArrayList::new, expectedWrapperClass); this(ArrayList::new, expectedWrapperClass);
} }
/* package */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ReflectListWrapper(Supplier<List<?>> listCreator, Class<W> expectedWrapperClass) { /* package */ ReflectListWrapper(Supplier<List<?>> listCreator, Class<W> expectedWrapperClass) {
this((List<Object>) (listCreator == null ? new ArrayList<>() : listCreator.get()), expectedWrapperClass); this((List<Object>) (listCreator == null ? new ArrayList<>() : listCreator.get()), expectedWrapperClass);
} }
/* package */ ReflectListWrapper(List<Object> wrappedList, Class<W> expectedWrapperClass) { /* package */ ReflectListWrapper(List<Object> wrappedList, Class<W> expectedWrapperClass) {
super(wrappedList, el -> ReflectWrapper.wrap(el, expectedWrapperClass), ReflectWrapper::unwrap); super(wrappedList, el -> ReflectWrapper.wrap(el, expectedWrapperClass), ReflectWrapper::unwrap);
this.expectedWrapperClass = expectedWrapperClass; this.expectedWrapperClass = expectedWrapperClass;

View File

@ -10,25 +10,79 @@ import java.util.Objects;
import static fr.pandacube.lib.util.ThrowableUtil.wrapEx; import static fr.pandacube.lib.util.ThrowableUtil.wrapEx;
/**
* Superclass of all reflect wrapper objects.
*/
public abstract class ReflectWrapper implements ReflectWrapperI { public abstract class ReflectWrapper implements ReflectWrapperI {
private static final Map<Object, ReflectWrapperI> objectWrapperCache = new MapMaker().weakKeys().makeMap(); private static final Map<Object, ReflectWrapperI> objectWrapperCache = new MapMaker().weakKeys().makeMap();
/**
* Unwraps the object from the provided reflect wrapper.
* @param wr the reflect wrapper from which to get the object.
* @return the object from the provided reflect wrapper.
*/
public static Object unwrap(ReflectWrapperI wr) { public static Object unwrap(ReflectWrapperI wr) {
return wr == null ? null : wr.__getRuntimeInstance(); return wr == null ? null : wr.__getRuntimeInstance();
} }
/**
* Unwraps the object from the provided reflect wrapper.
* @param wr the reflect wrapper from which to get the object.
* @param <T> the type of the wrapped object.
* @return the object from the provided reflect wrapper.
*/
public static <T> T unwrap(ReflectWrapperTypedI<T> wr) { public static <T> T unwrap(ReflectWrapperTypedI<T> wr) {
return wr == null ? null : wr.__getRuntimeInstance(); return wr == null ? null : wr.__getRuntimeInstance();
} }
public static <W extends ReflectWrapperI> W wrap(Object runtimeObj) { /**
* Wraps the provided runtime object into a reflect wrapper.
* If a wrapper instance is already known, it will return it instead of instanciating a new one.
* It is better to call {@link #wrap(Object, Class)} if you know the type of wrapper needed.
* @param runtimeObj the object to wrap.
* @return the reflect wrapper wrapping the provided object.
* @throws ClassCastException if the runtime class of the object is not handled by the expected wrapper class or its
* subclasses.
* @throws IllegalArgumentException if the runtime class of the object is not handled by any of the registered
* wrapper classes.
*/
public static ReflectWrapperI wrap(Object runtimeObj) {
return wrap(runtimeObj, null); return wrap(runtimeObj, null);
} }
/**
* Wraps the provided runtime object (with has a known type) into a reflect wrapper.
* If a wrapper instance is already known, it will return it instead of instanciating a new one.
* It is better to call {@link #wrap(Object, Class)} if you know the type of wrapper needed.
* @param runtimeObj the object to wrap.
* @param expectedWrapperClass the reflect wrapper class expected to be returned.
* @param <W> the type of the reflect wrapper.
* @param <T> the type of the wrapped object.
* @return the reflect wrapper wrapping the provided object.
* @throws ClassCastException if the runtime class of the object is not handled by the expected wrapper class or its
* subclasses.
* @throws IllegalArgumentException if the runtime class of the object is not handled by any of the registered
* wrapper classes.
*/
public static <T, W extends ReflectWrapperTypedI<T>> W wrapTyped(T runtimeObj, Class<W> expectedWrapperClass) { public static <T, W extends ReflectWrapperTypedI<T>> W wrapTyped(T runtimeObj, Class<W> expectedWrapperClass) {
return wrap(runtimeObj, expectedWrapperClass); return wrap(runtimeObj, expectedWrapperClass);
} }
/**
* Wraps the provided runtime object into a reflect wrapper.
* If a wrapper instance is already known, it will return it instead of instanciating a new one.
* It is better to call {@link #wrap(Object, Class)} if you know the type of wrapper needed.
* @param runtimeObj the object to wrap.
* @param expectedWrapperClass the reflect wrapper class expected to be returned.
* @param <W> the type of the reflect wrapper.
* @return the reflect wrapper wrapping the provided object.
* @throws ClassCastException if the runtime class of the object is not handled by the expected wrapper class or its
* subclasses.
* @throws IllegalArgumentException if the runtime class of the object is not handled by any of the registered
* wrapper classes.
*/
public static <W extends ReflectWrapperI> W wrap(Object runtimeObj, Class<W> expectedWrapperClass) { public static <W extends ReflectWrapperI> W wrap(Object runtimeObj, Class<W> expectedWrapperClass) {
if (runtimeObj == null) if (runtimeObj == null)
return null; return null;
@ -73,6 +127,13 @@ public abstract class ReflectWrapper implements ReflectWrapperI {
} }
} }
/**
* Wraps the provided runtime list into a reflect list wrapper.
* @param runtimeList the list of runtime object to wrap.
* @param expectedWrapperClass the wrapper class of the objects in this list.
* @param <W> the type of reflect wrapper for the objects in this list.
* @return a reflect list wrapper wrapping the provided list.
*/
public static <W extends ReflectWrapperI> ReflectListWrapper<W> wrapList(List<Object> runtimeList, Class<W> expectedWrapperClass) { public static <W extends ReflectWrapperI> ReflectListWrapper<W> wrapList(List<Object> runtimeList, Class<W> expectedWrapperClass) {
return new ReflectListWrapper<>(runtimeList, expectedWrapperClass); return new ReflectListWrapper<>(runtimeList, expectedWrapperClass);
} }
@ -81,8 +142,21 @@ public abstract class ReflectWrapper implements ReflectWrapperI {
protected final Object reflectObject;
private final Object reflectObject;
/**
* Instanciate this Reflect Wrapper with the provided object.
* Any subclasses should not make their constructor public since the instanciation is managed by {@link #wrap(Object, Class) wrap(...)}.
* @param obj the object to wrap. It must be an instance of the {@link #__getRuntimeClass() runtime class} of this
* wrapper class.
*/
protected ReflectWrapper(Object obj) { protected ReflectWrapper(Object obj) {
Objects.requireNonNull(obj); Objects.requireNonNull(obj);
if (!__getRuntimeClass().isInstance(obj)) { if (!__getRuntimeClass().isInstance(obj)) {

View File

@ -1,11 +1,22 @@
package fr.pandacube.lib.reflect.wrapper; package fr.pandacube.lib.reflect.wrapper;
/**
* Interface implemented by all reflect wrapper objects.
*/
public interface ReflectWrapperI { public interface ReflectWrapperI {
/**
* Gets the class of the wrapped object.
* @return the class of the wrapped object.
*/
default Class<?> __getRuntimeClass() { default Class<?> __getRuntimeClass() {
return WrapperRegistry.getRuntimeClassOfWrapperClass(getClass()); return WrapperRegistry.getRuntimeClassOfWrapperClass(getClass());
} }
/**
* Returns the wrapped object.
* @return the wrapped object.
*/
Object __getRuntimeInstance(); Object __getRuntimeInstance();

View File

@ -1,7 +1,17 @@
package fr.pandacube.lib.reflect.wrapper; package fr.pandacube.lib.reflect.wrapper;
/**
* Superclass of all reflect wrapper objects which wrapped objet type is statically known.
* @param <T> the type (or supertype) of the wrapped object.
*/
public abstract class ReflectWrapperTyped<T> extends ReflectWrapper implements ReflectWrapperTypedI<T> { public abstract class ReflectWrapperTyped<T> extends ReflectWrapper implements ReflectWrapperTypedI<T> {
/**
* Instanciate this Reflect Wrapper with the provided object.
* Any subclasses should not make their constructor public since the instanciation is managed by {@link #wrap(Object, Class) wrap(...)}.
* @param obj the object to wrap. It must be an instance of the {@link #__getRuntimeClass() runtime class} of this
* wrapper class.
*/
protected ReflectWrapperTyped(Object obj) { protected ReflectWrapperTyped(Object obj) {
super(obj); super(obj);
} }

View File

@ -1,5 +1,9 @@
package fr.pandacube.lib.reflect.wrapper; package fr.pandacube.lib.reflect.wrapper;
/**
* Interface implemented by all reflect wrapper objects which wrapped objet type is statically known.
* @param <T> the type (or supertype) of the wrapped object.
*/
public interface ReflectWrapperTypedI<T> extends ReflectWrapperI { public interface ReflectWrapperTypedI<T> extends ReflectWrapperI {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override