diff --git a/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java index 53fdbf1..0da035e 100644 --- a/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java +++ b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java @@ -1,16 +1,12 @@ 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.suggestion.Suggestion; 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 fr.pandacube.lib.commands.BrigadierCommand; import fr.pandacube.lib.commands.SuggestionsSupplier; -import fr.pandacube.lib.reflect.Reflect; import fr.pandacube.lib.util.Log; import net.md_5.bungee.api.CommandSender; 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.TabExecutor; -import java.util.Arrays; import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Predicate; 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 { + /** + * The command dispatcher. + */ protected BungeeBrigadierDispatcher dispatcher; - + + /** + * Instanciate this command isntance. + * @param d the dispatcher in which to register this command. + */ public BungeeBrigadierCommand(BungeeBrigadierDispatcher d) { if (d == null) { throw new IllegalStateException("BungeeBrigadierDispatcher not provided."); @@ -62,12 +66,14 @@ public abstract class BungeeBrigadierCommand extends BrigadierCommand wrapSuggestions(SuggestionsSupplier suggestions) { return wrapSuggestions(suggestions, Function.identity()); } diff --git a/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java index 46cdbc6..a804409 100644 --- a/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java +++ b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java @@ -1,21 +1,7 @@ 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.commands.BrigadierCommand; 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.md_5.bungee.api.CommandSender; 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.event.EventHandler; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; - +/** + * Implementation of {@link BrigadierDispatcher} that integrates the commands into BungeeCord API, so the players and + * the console can actually execute them. + */ public class BungeeBrigadierDispatcher extends BrigadierDispatcher implements Listener { @@ -37,15 +22,20 @@ public class BungeeBrigadierDispatcher extends BrigadierDispatcher pm = StandalonePlayerManager.getInstance(); + AbstractPlayerManager pm = AbstractPlayerManager.getInstance(); if (pm != null) { - StandaloneOnlinePlayer op = pm.get(p.getUniqueId()); + AbstractOnlinePlayer op = pm.get(p.getUniqueId()); if (op != null) { world = op.getWorldName(); } diff --git a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java index 881c2f7..717de5a 100644 --- a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java +++ b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java @@ -3,9 +3,9 @@ package fr.pandacube.lib.bungee.players; import net.md_5.bungee.api.ProxyServer; 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 diff --git a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java index 67e9025..edb66bd 100644 --- a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java +++ b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java @@ -19,11 +19,11 @@ import net.md_5.bungee.protocol.packet.ClientSettings; import net.md_5.bungee.protocol.packet.PluginMessage; 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.util.MinecraftVersion; -public interface BungeeOnlinePlayer extends BungeeOffPlayer, StandaloneOnlinePlayer { +public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlayer { /* * General data and state @@ -134,7 +134,7 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, StandaloneOnlinePla return new BungeeClientOptions(this); } - class BungeeClientOptions implements StandaloneOnlinePlayer.ClientOptions { + class BungeeClientOptions implements AbstractOnlinePlayer.ClientOptions { private final BungeeOnlinePlayer op; public BungeeClientOptions(BungeeOnlinePlayer op) { diff --git a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java index 54f49cc..a7dbfc7 100644 --- a/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java +++ b/pandalib-chat/src/main/java/fr/pandacube/lib/chat/ChatTreeNode.java @@ -4,7 +4,7 @@ import java.util.ArrayList; 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 { diff --git a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java index f824753..5e79859 100644 --- a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java +++ b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java @@ -6,91 +6,111 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ParsedCommandNode; -import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import com.mojang.brigadier.suggestion.Suggestion; 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 fr.pandacube.lib.reflect.Reflect; import fr.pandacube.lib.util.Log; -import java.util.ArrayList; 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.Predicate; /** * 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 environment’s Brigadier instance, during the - * instantiation of this object. + * Subclasses may use any mechanism to integrate this command in the environment’s Brigadier instance (or in a + * {@link BrigadierDispatcher} instance), during the instantiation of this object. * @param the command source (or command sender) type. */ public abstract class BrigadierCommand { - - - - + /** + * 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 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()}. */ protected void postBuildCommand(LiteralCommandNode commandNode) { // 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() { 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 literal(String 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 the argument type. + * @return a new {@link RequiredArgumentBuilder} that has the provided name. + */ public RequiredArgumentBuilder argument(String name, ArgumentType type) { return RequiredArgumentBuilder.argument(name, type); } - - - - - - + /** + * Tells if the provided command sender is a player. + * @param sender the sender to test if it’s a player or not. + * @return true if the sender is a player, false otherwise. + */ public abstract boolean isPlayer(S sender); + /** + * Tells if the provided command sender is the console. + * @param sender the sender to test if it’s the console or not. + * @return true if the sender is the console, false otherwise. + */ 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 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 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 hasPermission(String permission); @@ -99,8 +119,15 @@ public abstract class BrigadierCommand { - - + /** + * Determines if the literal node is found in the command. + *

+ * 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. + * @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) { for (ParsedCommandNode node : context.getNodes()) { if (node.getNode() instanceof LiteralCommandNode lNode @@ -111,24 +138,60 @@ public abstract class BrigadierCommand { } - - - - - - + /** + * 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 the argument type. + * @return the value of the argument, or null if not found. + */ public static T tryGetArgument(CommandContext context, String argument, Class type) { return tryGetArgument(context, argument, type, Function.identity(), null); } - public static T tryGetArgument(CommandContext context, String argument, Class sourceType, Function 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 the argument type. + * @return the value of the argument, or {@code deflt} if not found. + */ public static T tryGetArgument(CommandContext context, String argument, Class type, T 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 the argument type in the command context. + * @param the returned type. + * @return the value of the argument, transformed by {@code transformIfFound}, or null if not found. + */ + public static T tryGetArgument(CommandContext context, String argument, Class sourceType, Function 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 the argument type in the command context. + * @param the returned type. + * @return the value of the argument, transformed by {@code transformIfFound}, or {@code deflt} if not found. + */ public static T tryGetArgument(CommandContext context, String argument, Class sourceType, Function transformIfFound, T deflt) { ST sourceValue; try { @@ -140,25 +203,25 @@ public abstract class BrigadierCommand { } - - - - - - - - + /** + * 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) { return new SimpleCommandExceptionType(new LiteralMessage(message)).create(); } - - - - - + /** + * Wraps the provided {@link SuggestionsSupplier} into a Brigadier’s {@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 the type of command sender supported by the {@link SuggestionsSupplier}. + */ protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions, Function senderUnwrapper) { return (context, builder) -> { AS sender = senderUnwrapper.apply(context.getSource()); diff --git a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java index 7a42bc4..34d1827 100644 --- a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java +++ b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java @@ -11,28 +11,41 @@ import net.kyori.adventure.text.ComponentLike; 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 the command source (or command sender) type. + */ public abstract class BrigadierDispatcher { private final CommandDispatcher dispatcher = new CommandDispatcher<>(); - + /** + * Registers the provided command node into this dispatcher. + * @param node the node to register. + */ public void register(LiteralCommandNode node) { dispatcher.getRoot().addChild(node); } - - + /** + * Returns the Brigadier dispatcher. + * @return the Brigadier dispatcher. + */ public CommandDispatcher getDispatcher() { 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) { ParseResults parsed = dispatcher.parse(commandWithoutSlash, sender); @@ -46,11 +59,15 @@ public abstract class BrigadierDispatcher { Log.severe(e); 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) { ParseResults parsed = dispatcher.parse(buffer, sender); try { @@ -64,10 +81,11 @@ public abstract class BrigadierDispatcher { } - - - - + /** + * 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); } diff --git a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java index 995c181..b6b3ee2 100644 --- a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java +++ b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java @@ -1,5 +1,6 @@ package fr.pandacube.lib.commands; +import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.StringRange; @@ -22,7 +23,16 @@ import java.util.concurrent.CompletableFuture; public class BrigadierSuggestionsUtil { - + /** + * Gets suggestions with the provided parsed command. + *

+ * 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 the sender type. + * @see CommandDispatcher#getCompletionSuggestions(ParseResults, int) + */ public static CompletableFuture buildSuggestionBrigadier(ParseResults parsed) { int cursor = parsed.getReader().getTotalLength(); final CommandContextBuilder context = parsed.getContext(); @@ -55,7 +65,17 @@ public class BrigadierSuggestionsUtil { return result; } - // inspired from com.mojang.brigadier.suggestion.Suggestions#merge, but without the sorting part + + /** + * Merges the provided {@link Suggestions}. + *

+ * 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) { if (suggestions.isEmpty()) { 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}. + *

+ * 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 suggestions) { if (suggestions.isEmpty()) { return new Suggestions(StringRange.at(0), new ArrayList<>(0)); @@ -96,7 +124,7 @@ public class BrigadierSuggestionsUtil { - public static CompletableFuture completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) { + /* package */ static CompletableFuture completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) { return CompletableFuture.completedFuture( createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder)) ); diff --git a/pandalib-db/pom.xml b/pandalib-db/pom.xml index df38184..f240817 100644 --- a/pandalib-db/pom.xml +++ b/pandalib-db/pom.xml @@ -19,6 +19,11 @@ pandalib-util ${project.version} + + fr.pandacube.lib + pandalib-reflect + ${project.version} + org.apache.commons commons-dbcp2 diff --git a/pandalib-db/src/main/java/fr/pandacube/lib/db/DB.java b/pandalib-db/src/main/java/fr/pandacube/lib/db/DB.java index 6293ba0..1dbf94f 100644 --- a/pandalib-db/src/main/java/fr/pandacube/lib/db/DB.java +++ b/pandalib-db/src/main/java/fr/pandacube/lib/db/DB.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import fr.pandacube.lib.reflect.Reflect; import fr.pandacube.lib.util.Log; /** @@ -576,7 +577,7 @@ public final class DB { @SuppressWarnings("unchecked") private static > E getElementInstance(ResultSet set, Class elemClass) throws DBException { 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(); diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java index 0f74512..cd7e0f4 100644 --- a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PandalibPaperPermissions.java @@ -8,6 +8,8 @@ import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.function.Predicate; +import fr.pandacube.lib.db.DB; +import fr.pandacube.lib.db.DBConnection; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; 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.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)}. + * Don’t forget that the permission system also needs a connection to a database, so don’t forget to call + * {@link DB#init(DBConnection, String)} with the appropriate parameters before anything. + */ public class PandalibPaperPermissions implements Listener { /* package */ static JavaPlugin plugin; /* package */ static String serverName; /* package */ static final Map 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) { PandalibPaperPermissions.plugin = plugin; PandalibPaperPermissions.serverName = serverName; @@ -58,6 +74,10 @@ public class PandalibPaperPermissions implements Listener { } } + /** + * Player login event handler. + * @param event the event. + */ @EventHandler(priority = EventPriority.LOWEST) public void onPlayerLogin(PlayerLoginEvent event) { 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) public void onPlayerQuit(PlayerQuitEvent event) { PermissionsInjectorBukkit.uninject(event.getPlayer()); diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java index 045539a..44d78ae 100644 --- a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorBukkit.java @@ -29,7 +29,7 @@ import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.reflect.Reflect; 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 diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java index 970d3e4..298515f 100644 --- a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorVault.java @@ -10,7 +10,7 @@ import fr.pandacube.lib.permissions.PermGroup; import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.util.Log; -public class PermissionsInjectorVault { +/* package */ class PermissionsInjectorVault { public static PandaVaultPermission permInstance; @@ -27,12 +27,12 @@ public class PermissionsInjectorVault { Log.warning("Vault plugin not detected. Not using it to provide permissions and prefix/suffix." + e.getMessage()); } } - - - - - - public static class PandaVaultPermission extends net.milkbowl.vault.permission.Permission { + + + + + + /* package */ static class PandaVaultPermission extends net.milkbowl.vault.permission.Permission { private PandaVaultPermission() { } diff --git a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java index afb3af2..83892c5 100644 --- a/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java +++ b/pandalib-paper-permissions/src/main/java/fr/pandacube/lib/paper/permissions/PermissionsInjectorWEPIF.java @@ -13,13 +13,11 @@ import fr.pandacube.lib.permissions.PermPlayer; import fr.pandacube.lib.permissions.Permissions; import fr.pandacube.lib.util.Log; -public class PermissionsInjectorWEPIF { - - public static PandaWEPIFPermissionsProvider permInstance; +/* package */ class PermissionsInjectorWEPIF { public static void inject() { try { - permInstance = new PandaWEPIFPermissionsProvider(); + PandaWEPIFPermissionsProvider permInstance = new PandaWEPIFPermissionsProvider(); Bukkit.getServicesManager().register(com.sk89q.wepif.PermissionsProvider.class, permInstance, PandalibPaperPermissions.plugin, ServicePriority.Highest); Log.info("Providing permissions through WEPIF"); @@ -32,11 +30,11 @@ public class PermissionsInjectorWEPIF { } } - - - - - public static class PandaWEPIFPermissionsProvider implements com.sk89q.wepif.PermissionsProvider { + + + + + /* package */ static class PandaWEPIFPermissionsProvider implements com.sk89q.wepif.PermissionsProvider { private PandaWEPIFPermissionsProvider() { } private PermPlayer getPlayer(OfflinePlayer player) { diff --git a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java index 4689b66..90c910e 100644 --- a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java +++ b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java @@ -5,9 +5,9 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; 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 diff --git a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java index 452e768..ba720e7 100644 --- a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java +++ b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java @@ -21,9 +21,9 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; 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 @@ -119,7 +119,7 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, StandaloneOnlinePlaye @Override PaperClientOptions getClientOptions(); - abstract class PaperClientOptions implements StandaloneOnlinePlayer.ClientOptions { + abstract class PaperClientOptions implements AbstractOnlinePlayer.ClientOptions { private final PaperOnlinePlayer op; diff --git a/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/server/ServerChunkCache.java b/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/server/ServerChunkCache.java index 26f9e74..f7737e0 100644 --- a/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/server/ServerChunkCache.java +++ b/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/server/ServerChunkCache.java @@ -16,6 +16,6 @@ public class ServerChunkCache extends ReflectWrapper { protected ServerChunkCache(Object obj) { super(obj); - chunkMap = wrap(wrapReflectEx(() -> FIELD_chunkMap.getValue(obj))); + chunkMap = wrap(wrapReflectEx(() -> FIELD_chunkMap.getValue(obj)), ChunkMap.class); } } diff --git a/pandalib-permissions/pom.xml b/pandalib-permissions/pom.xml index 2d4b490..d51456f 100644 --- a/pandalib-permissions/pom.xml +++ b/pandalib-permissions/pom.xml @@ -65,13 +65,14 @@ META-INF/*.SF META-INF/*.DSA META-INF/*.RSA + META-INF/MANIFEDT.MF com.fathzer.soft.javaluator - fr.pandacube.shaded.javaluator + fr.pandacube.lib.permissions.shaded.javaluator diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java index dd67cf3..74494e0 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermEntity.java @@ -8,98 +8,219 @@ import java.util.Set; import java.util.stream.LongStream; 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.SQLPermissions.EntityType; import fr.pandacube.lib.util.Log; -public abstract class PermEntity { - protected final String name; - protected final EntityType type; - protected PermEntity(String n, EntityType t) { +/** + * Represents an entity in the permission system, either a group or a player. + */ +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; } - 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 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 getInheritancesString(); + /** + * Gets the name of this entity. + * @return the name of this entity. + */ 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() { 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 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) { if (group == null) return false; 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() { 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() { 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() { return Permissions.resolver.debugPrefix(name, type); } - - + + /** + * Sets the prefix of this entity. + * @param prefix the prefix for this entity. + */ public void setSelfPrefix(String 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() { 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() { 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() { return Permissions.resolver.debugSuffix(name, type); } - - + + /** + * Sets the suffix of this entity. + * @param suffix the suffix for this entity. + */ public void setSelfSuffix(String 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 listEffectivePermissions() { 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 listEffectivePermissions(String server) { 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 listEffectivePermissions(String server, String 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) { 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) { 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) { String prefixWithEndingDot = permissionPrefix.endsWith(".") ? permissionPrefix : (permissionPrefix + "."); int prefixLength = prefixWithEndingDot.length(); @@ -120,106 +241,299 @@ public abstract class PermEntity { .mapToLong(longSuffix -> longSuffix) .sorted(); } - - + + + /** + * 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) { 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) { 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) { 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) { 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) { 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) { Boolean ret = Permissions.resolver.getEffectivePermission(name, type, permission, server, world); Log.debug("[Perm] For " + type.toString().toLowerCase() + " " + getName() + ", '" + permission + "' is " + 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) { Boolean ret = hasPermission(permission, server, world); 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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() { return getSelfPermissionsServerWorldKeys().stream() - .mapToInt(key -> getSelfPermissions(key.server, key.world).size()) + .mapToInt(key -> getSelfPermissions(key.server(), key.world()).size()) .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 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 getSelfPermissions() { 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 getSelfPermissions(String server) { 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 getSelfPermissions(String server, String world) { return getBackendEntity().getSelfPermissions(server, world); } - + + + + + @Override public boolean equals(Object obj) { diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermGroup.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermGroup.java index 5d8a234..ba05f8d 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermGroup.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermGroup.java @@ -6,7 +6,10 @@ import java.util.stream.Collectors; import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedGroup; 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) { super(name, EntityType.Group); } @@ -31,27 +34,54 @@ public class PermGroup extends PermEntity { .map(cg -> cg.name) .collect(Collectors.toList()); } - + + /** + * Tells if this group is a default group. + * A player inherits all default groups when they don’t explicitely inherit from at least one group. + * @return true if this group is a default group, false otherwise. + */ public boolean isDefault() { return getBackendEntity().deflt; } - + + /** + * Sets this group as a default group or not. + * All players that don’t 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) { 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) { 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) { 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) { 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) { removeInheritance(group.name); } diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermPlayer.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermPlayer.java index bdabecf..c12f554 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermPlayer.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermPlayer.java @@ -7,7 +7,10 @@ import java.util.stream.Collectors; import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer; 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; /* package */ PermPlayer(UUID id) { super(id.toString(), EntityType.User); @@ -29,7 +32,11 @@ public class PermPlayer extends PermEntity { .map(cg -> cg.name) .collect(Collectors.toList()); } - + + /** + * Gets the UUID of this player. + * @return the UUID of this player. + */ public UUID getPlayerId() { return playerId; } @@ -48,14 +55,18 @@ public class PermPlayer extends PermEntity { } /** + * Gets all the groups this player belongs to. * Alias for {@link #getInheritances()}. + * @return a list of all the groups this player belongs to. */ public List getGroups() { return getInheritances(); } /** + * Gets all the group names this player belongs to. * Alias for {@link #getInheritances()}. + * @return a list of all the group names this player belongs to. */ public List getGroupsString() { return getInheritancesString(); @@ -65,35 +76,67 @@ public class PermPlayer extends PermEntity { * Tells if the player is directly part of a group. * This is equivalent to {@link #inheritsFromGroup(String, boolean) inheritsFromGroup(group, false)} * @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) { 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() { 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) { 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) { 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) { 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) { 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) { 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) { removeGroup(group.name); } diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionExpressionParser.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionExpressionParser.java index e60553a..7f1341d 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionExpressionParser.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionExpressionParser.java @@ -10,10 +10,38 @@ import com.fathzer.soft.javaluator.Operator; import com.fathzer.soft.javaluator.Operator.Associativity; import com.fathzer.soft.javaluator.Parameters; +/** + * Class that evaluates a permission string as if it was a boolean expression with permission nodes as variables. + *

+ * 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: + *

{@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"
+ * }
+ * Notice that spaces around permission nodes and operators does not affect the results of the parsing. + */ public class PermissionExpressionParser { 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) { try { 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 { } @@ -89,41 +120,4 @@ public class PermissionExpressionParser { } } - - /* TODO move to test code - public static void main(String[] args) { - java.util.List 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(); - } - } - } - */ - } diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/Permissions.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/Permissions.java index 48d1088..331229f 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/Permissions.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/Permissions.java @@ -10,6 +10,13 @@ import fr.pandacube.lib.db.DBConnection; import fr.pandacube.lib.db.DBException; import fr.pandacube.lib.util.Log; +/** + * Main class for the Pandalib permission system. + *

+ * 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 { /* package */ static PermissionsCachedBackendReader backendReader; @@ -20,6 +27,10 @@ public class Permissions { /** * Initialize the permission system. * 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 playerNameGetter) throws DBException { Permissions.playerNameGetter = playerNameGetter == null ? UUID::toString : playerNameGetter; @@ -37,7 +48,18 @@ public class Permissions { throw e; } } - + + 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) { checkInitialized(); if (specialPermissions == null) @@ -45,18 +67,23 @@ public class Permissions { resolver.specialPermissions.addAll(Arrays.asList(specialPermissions)); } - 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."); - } - } - + /** + * Clears the cached data of a specific player. + * @param playerId the UUID of the player. + * @throws IllegalStateException if the permission system was not initialized properly. + */ public static void clearPlayerCache(UUID playerId) { checkInitialized(); backendReader.clearPlayerCache(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) { checkInitialized(); backendReader.clearAndResetCacheAsync(() -> { @@ -65,13 +92,25 @@ public class Permissions { then.run(); }); } - - + + /** + * 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) { checkInitialized(); 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) { checkInitialized(); Thread t = new Thread(() -> { @@ -84,23 +123,45 @@ public class Permissions { t.setDaemon(true); 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) { checkInitialized(); 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 getGroups() { checkInitialized(); 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 getDefaultGroups() { checkInitialized(); 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 getFullPermissionsList() { + checkInitialized(); return backendReader.getFullPermissionsList(); } diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java index 3d7424d..b992e9e 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/PermissionsResolver.java @@ -25,7 +25,7 @@ import fr.pandacube.lib.permissions.PermissionsCachedBackendReader.CachedPlayer; import fr.pandacube.lib.permissions.SQLPermissions.EntityType; import fr.pandacube.lib.util.Log; -public class PermissionsResolver { +/* package */ class PermissionsResolver { private final PermissionsCachedBackendReader backendReader; @@ -400,7 +400,7 @@ public class PermissionsResolver { ParsedSelfPermission specialPerm = null; for (SpecialPermission spePerm : specialPermissions) { - if (spePerm.match().match(permission)) { + if (spePerm.matcher().match(permission)) { boolean res = spePerm.tester().test(permP, permission, server, world); specialPerm = new ParsedSelfPermission(permission, res, PermType.SPECIAL); break; diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SQLPermissions.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SQLPermissions.java index ecb497b..070a064 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SQLPermissions.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SQLPermissions.java @@ -3,13 +3,19 @@ package fr.pandacube.lib.permissions; import fr.pandacube.lib.db.SQLElement; import fr.pandacube.lib.db.SQLField; +/** + * SQL Table to store the permissions data. + */ public class SQLPermissions extends SQLElement { + /** + * Instanciate a new entry in the table. + */ public SQLPermissions() { super(); } - public SQLPermissions(int id) { + private SQLPermissions(int id) { super(id); } @@ -18,22 +24,45 @@ public class SQLPermissions extends SQLElement { return "permissions"; } + /** The name of the entity (player id or group name). */ public static final SQLField name = field(VARCHAR(64), false); + /** The entity type, based on {@link EntityType}. */ public static final SQLField type = field(TINYINT, false); + /** The key of the data ("permission", "inheritance", …). */ public static final SQLField key = field(VARCHAR(256), false); + /** The data value (permission node, name of inherited group, prefix/suffix, …). */ public static final SQLField value = field(VARCHAR(256), false); + /** The server in which the permission apply. */ public static final SQLField server = field(VARCHAR(64), true); + /** The world in which the permission apply. */ public static final SQLField world = field(VARCHAR(64), true); - + /** + * All possible type of entity type. + */ public enum EntityType { + /** + * User entity type. + */ User, + /** + * Group entity type. + */ Group; + /** + * Returns the database value of this entity type. + * @return the database value of this entity type. + */ public int getCode() { 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) { if (code >= 0 && code < values().length) return values()[code]; diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/ServerWorldKey.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/ServerWorldKey.java index 08ba378..0d6b8c1 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/ServerWorldKey.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/ServerWorldKey.java @@ -3,11 +3,14 @@ package fr.pandacube.lib.permissions; import java.util.Comparator; import java.util.Objects; -public class ServerWorldKey implements Comparable { - public final String server, world; - ServerWorldKey(String s, String w) { - server = s; world = w; - } +/** + * A pair of string representing a server and world name, used to organize and filter the permission data of a player or + * group. + * @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 { + @Override public boolean equals(Object obj) { return obj instanceof ServerWorldKey o diff --git a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SpecialPermission.java b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SpecialPermission.java index 976a2e2..8fa47fe 100644 --- a/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SpecialPermission.java +++ b/pandalib-permissions/src/main/java/fr/pandacube/lib/permissions/SpecialPermission.java @@ -2,14 +2,35 @@ package fr.pandacube.lib.permissions; /** * 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 { + /** + * 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); } - + + /** + * Predicate that tell the value of this special permission, based on the parameters. + */ 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); } diff --git a/pandalib-permissions/src/test/java/fr/pandacube/lib/permissions/PermissionExpressionParserTest.java b/pandalib-permissions/src/test/java/fr/pandacube/lib/permissions/PermissionExpressionParserTest.java new file mode 100644 index 0000000..0ead916 --- /dev/null +++ b/pandalib-permissions/src/test/java/fr/pandacube/lib/permissions/PermissionExpressionParserTest.java @@ -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 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); + } +} \ No newline at end of file diff --git a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java index 74f12c7..650020b 100644 --- a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java +++ b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOffPlayer.java @@ -6,9 +6,12 @@ import java.util.stream.LongStream; import fr.pandacube.lib.chat.ChatColorUtil; import fr.pandacube.lib.permissions.PermPlayer; 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, * generated using eventual permission’s prefix(es) and suffix(es) of the player, * and with color codes translated to Minecraft’s native {@code §}. + * @return the display name of this player, generated by the permission system. */ default String getDisplayNameFromPermissionSystem() { PermPlayer permU = getPermissionUser(); @@ -127,6 +131,8 @@ public interface PermissibleOffPlayer extends StandaloneOffPlayer { /** * 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) { 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 * @return true if this player is part of the group, * false otherwise diff --git a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java index ac56790..6385d7f 100644 --- a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java +++ b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissibleOnlinePlayer.java @@ -4,9 +4,12 @@ import java.util.OptionalLong; import java.util.stream.LongStream; 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 * (Bukkit/Bungee) to get the name of the player. */ diff --git a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java b/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java deleted file mode 100644 index baf0b41..0000000 --- a/pandalib-players-permissible/src/main/java/fr/pandacube/lib/players/permissible/PermissiblePlayerManager.java +++ /dev/null @@ -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 extends StandalonePlayerManager { - 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. - *

- * 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. - *

- * This method assumes this message is not caused by a specific player. To specify the source player, use - * {@link #broadcast(ComponentLike, boolean, String, UUID)}. - *

- * 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. - *

- * 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); - } - - - - - - -} diff --git a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java similarity index 51% rename from pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java rename to pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java index bed6f78..04d80e9 100644 --- a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOffPlayer.java +++ b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java @@ -2,7 +2,10 @@ package fr.pandacube.lib.players.standalone; 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 */ 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(); /** - * 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 + * 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. */ boolean isOnline(); @@ -42,8 +45,9 @@ public interface StandaloneOffPlayer { /** * Return the online instance of this player, if any exists. * 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 - * (and team for bukkit implementation) - * @return the display name of the player + * Returns the display name of the player (if any), with eventual prefix and suffix depending on permission groups + * (and team for bukkit implementation). + * @return the display name of the player. */ String getDisplayName(); diff --git a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOnlinePlayer.java similarity index 53% rename from pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java rename to pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOnlinePlayer.java index d9b78c3..62293fd 100644 --- a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandaloneOnlinePlayer.java +++ b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOnlinePlayer.java @@ -9,7 +9,10 @@ import net.kyori.adventure.text.ComponentLike; import java.util.Locale; import java.util.UUID; -public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { +/** + * Represents any online player. + */ +public interface AbstractOnlinePlayer extends AbstractOffPlayer { @@ -17,15 +20,19 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { /* * General data and state */ - + /** - * @return The current name of this player - * @apiNote The implementation is expected to call the environment API - * (Bukkit/Bungee) to get the name of the player. + * Returns the name of the current server the player is in. + * The returned value is used by the 'pandalib-permissions' system. + * @return the name of the current server the player is in. */ - 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(); @@ -40,6 +47,8 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { /** * 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), * 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 player’s chat, if - * the chat is activated. + * Display the provided message in the player’s chat, if the chat is activated. * @param message the message to display. */ void sendMessage(Component message); /** - * Display the provided message in the player’s chat, if - * the chat is activated. + * Display the provided message in the player’s chat, if the chat is activated. * @param message the message to display. */ default void sendMessage(ComponentLike message) { @@ -74,8 +81,7 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { } /** - * Display the provided message in the player’s chat, if - * they allows to display CHAT messages + * Display the provided message in the player’s chat, if they allows to display CHAT messages. * @param message the message to display. * @param sender the player causing the send of this message. Client side filtering may occur. * May be null if we don’t 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); /** - * Display the provided message in the player’s chat, if - * they allows to display CHAT messages + * Display the provided message in the player’s chat, if they allows to display CHAT messages. * @param message the message to display * @param sender the player causing the send of this message. Client side filtering may occur. * May be null if we don’t 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 player’s chat, if the chat is - * activated, prepended with the server prefix. + * Display the provided message in the player’s chat, if the chat is activated, prepended with the server prefix. * @param message the message to display */ 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 - * (third line in 1.15 debug screen). Supports ChatColor codes but no - * line break. + * Update the server brand field in the debug menu (F3) of the player (third line in 1.15 debug screen). + * Supports legacy section format but no line break. * @param brand the server brand to send to the client. */ void sendServerBrand(String brand); @@ -148,47 +151,57 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { /* * Client options */ - - + + /** + * Gets the current client options of this player. + * @return the current client options of this player. + */ ClientOptions getClientOptions(); - + + /** + * Interface providing various configuration values of the Minecraft client. + */ interface ClientOptions { - + + /** + * The language of the client interface. + * @return language of the client interface. + */ Locale getLocale(); - + + /** + * The client view distance, in chunks. + * @return client view distance, in chunks. + */ int getViewDistance(); - - - + + + /** + * If the chat displays the text colors. + * @return true if the chat displays the text colors, false otherwise. + */ boolean hasChatColorEnabled(); /** - * Tells if the client is configured to completely hide the chat to the - * player. When this is the case, nothing is displayed in the chat box, - * and the player can’t send any message or command. - * @implSpec if the value is unknown, it is assumed that the chat is - * fully visible. + * Tells if the client is configured to completely hide the chat to the player. When this is the case, nothing + * is displayed in the chat box, and the player can’t send any message or command. + * @return true if the chat is fully hidden, false otherwise. */ boolean isChatHidden(); /** - * Tells if the client is configured to display the chat normally. - * When this is the case, chat messages and system messages are - * displayed in the chat box, and the player can send messages and - * commands. - * @implSpec if the value is unknown, it is assumed that the chat is - * fully visible. + * Tells if the client is configured to display the chat normally. When this is the case, chat messages and + * system messages are displayed in the chat box, and the player can send messages and commands. + * @return true if the chat is fully visible, false otherwise. */ boolean isChatFullyVisible(); /** - * Tells if the client is configured to only display system messages - * in the chat. - * When this is the case, chat messages are hidden but system messages - * are visible in the chat box, and the player can only send commands. - * @implSpec if the value is unknown, it is assumed that the chat is - * fully visible. + * Tells if the client is configured to only display system messages in the chat. + * When this is the case, chat messages are hidden but system messages 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. */ boolean isChatOnlyDisplayingSystemMessages(); @@ -196,57 +209,85 @@ public interface StandaloneOnlinePlayer extends StandaloneOffPlayer { /** * 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 - * is on the right. + * @return true if the player’s character is left handed, false otherwise. */ boolean isLeftHanded(); /** * 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 - * is on the right. + * @return true if the player’s character is right handed, false otherwise. */ boolean isRightHanded(); /** * 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(); /** - * Tells if the client allows the server to list their player name in the - * multiplayer menu. + * Tells if the client allows the server to list their player name in the multiplayer menu. + * To respect the player privacy’s configuration, this configuration value must be verified when generating + * custom ping response packet (MOTD in multiplayer server’s menu) that includes player names. + * @return true if the client allows the server to list their player name in the multiplayer menu, false + * otherwise. */ boolean allowsServerListing(); - - - + + + /** + * Tells if the cape is enabled on the player’s skin. + * @return true if the cape is enabled on the player’s skin, false otherwise. + */ boolean hasSkinCapeEnabled(); - + + /** + * Tells if the jacket is enabled on the player’s skin. + * @return true if the jacket is enabled on the player’s skin, false otherwise. + */ boolean hasSkinJacketEnabled(); - + + /** + * Tells if the left sleeve is enabled on the player’s skin. + * @return true if the left sleeve is enabled on the player’s skin, false otherwise. + */ boolean hasSkinLeftSleeveEnabled(); - + + /** + * Tells if the right sleeve is enabled on the player’s skin. + * @return true if the right sleeve is enabled on the player’s skin, false otherwise. + */ boolean hasSkinRightSleeveEnabled(); - + + /** + * Tells if the left pants is enabled on the player’s skin. + * @return true if the left pants is enabled on the player’s skin, false otherwise. + */ boolean hasSkinLeftPantsEnabled(); - + + /** + * Tells if the right pants is enabled on the player’s skin. + * @return true if the right pants is enabled on the player’s skin, false otherwise. + */ boolean hasSkinRightPantsEnabled(); - + + /** + * Tells if the hat is enabled on the player’s skin. + * @return true if the hat is enabled on the player’s skin, false otherwise. + */ boolean hasSkinHatsEnabled(); } /** - * Tells if the player can send chat messages or receive chat messages from - * other players, according to their client configuration. - *
- * Chat messages represent public communication between players. By default, - * it only include actual chat message. This method may be used in commands - * like /me, /afk or the login/logout broadcasted messages + * Tells if the player can send chat messages or receive chat messages from other players, according to their client + * configuration. + *

+ * Chat messages represent public communication between players. By default, it only include actual chat message. + * This method may be used in commands 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() { return getClientOptions().isChatFullyVisible(); diff --git a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractPlayerManager.java b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractPlayerManager.java new file mode 100644 index 0000000..dfcf82b --- /dev/null +++ b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractPlayerManager.java @@ -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 the type of subclass of {@link AbstractOnlinePlayer} + * @param the type of subclass of {@link AbstractOffPlayer} + */ +public abstract class AbstractPlayerManager { + 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 onlinePlayers = Collections.synchronizedMap(new HashMap<>()); + + private final LoadingCache 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 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. + *

+ * 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. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(ComponentLike, boolean, String, UUID)}. + *

+ * 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. + *

+ * 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. + *

+ * This method doesn’t 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. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(ComponentLike, boolean, boolean, UUID)}. + *

+ * This method doesn’t 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. + *

+ * This method sends the message to the console. To change this behaviour, use + * {@link #broadcast(ComponentLike, boolean, boolean, UUID)}. + *

+ * This method doesn’t 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. + *

+ * This method assumes this message is not caused by a specific player. To specify the source player, use + * {@link #broadcast(ComponentLike, boolean, UUID)}. + *

+ * This method sends the message to the console. To change this behaviour, use + * {@link #broadcast(ComponentLike, boolean, boolean)}. + *

+ * This method doesn’t 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); + } + +} diff --git a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java deleted file mode 100644 index 1869e59..0000000 --- a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/StandalonePlayerManager.java +++ /dev/null @@ -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 { - 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 onlinePlayers = Collections.synchronizedMap(new HashMap<>()); - - private final LoadingCache 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 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. - *

- * 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. - *

- * 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. - *

- * This method assumes this message is not caused by a specific player. To specify the source player, use - * {@link #broadcast(ComponentLike, boolean, UUID)}. - *

- * 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); - } - - - - - - -} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ConcreteWrapper.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ConcreteWrapper.java index fb14858..149e56c 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ConcreteWrapper.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ConcreteWrapper.java @@ -12,5 +12,9 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) 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 value(); } diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectListWrapper.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectListWrapper.java index bc9fe88..e78af6b 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectListWrapper.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectListWrapper.java @@ -6,6 +6,11 @@ import java.util.function.Supplier; 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 the type of the reflect wrapper for the elements of this list. + */ public class ReflectListWrapper extends MappedListView implements ReflectWrapperTypedI> { private final Class expectedWrapperClass; @@ -14,11 +19,12 @@ public class ReflectListWrapper extends MappedListVie this(ArrayList::new, expectedWrapperClass); } - /* package */ + @SuppressWarnings("unchecked") - ReflectListWrapper(Supplier> listCreator, Class expectedWrapperClass) { + /* package */ ReflectListWrapper(Supplier> listCreator, Class expectedWrapperClass) { this((List) (listCreator == null ? new ArrayList<>() : listCreator.get()), expectedWrapperClass); } + /* package */ ReflectListWrapper(List wrappedList, Class expectedWrapperClass) { super(wrappedList, el -> ReflectWrapper.wrap(el, expectedWrapperClass), ReflectWrapper::unwrap); this.expectedWrapperClass = expectedWrapperClass; diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapper.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapper.java index e10bf1f..2af7d67 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapper.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapper.java @@ -10,25 +10,79 @@ import java.util.Objects; import static fr.pandacube.lib.util.ThrowableUtil.wrapEx; +/** + * Superclass of all reflect wrapper objects. + */ public abstract class ReflectWrapper implements ReflectWrapperI { private static final Map 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) { 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 the type of the wrapped object. + * @return the object from the provided reflect wrapper. + */ public static T unwrap(ReflectWrapperTypedI wr) { return wr == null ? null : wr.__getRuntimeInstance(); } - public static 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); } + + /** + * 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 the type of the reflect wrapper. + * @param 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 > W wrapTyped(T runtimeObj, Class 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 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 wrap(Object runtimeObj, Class expectedWrapperClass) { if (runtimeObj == null) return null; @@ -73,16 +127,36 @@ 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 the type of reflect wrapper for the objects in this list. + * @return a reflect list wrapper wrapping the provided list. + */ public static ReflectListWrapper wrapList(List runtimeList, Class expectedWrapperClass) { return new ReflectListWrapper<>(runtimeList, expectedWrapperClass); } - - - 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) { Objects.requireNonNull(obj); if (!__getRuntimeClass().isInstance(obj)) { diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperI.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperI.java index bf441a2..d7ffdd4 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperI.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperI.java @@ -1,11 +1,22 @@ package fr.pandacube.lib.reflect.wrapper; +/** + * Interface implemented by all reflect wrapper objects. + */ public interface ReflectWrapperI { - + + /** + * Gets the class of the wrapped object. + * @return the class of the wrapped object. + */ default Class __getRuntimeClass() { return WrapperRegistry.getRuntimeClassOfWrapperClass(getClass()); } - + + /** + * Returns the wrapped object. + * @return the wrapped object. + */ Object __getRuntimeInstance(); diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTyped.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTyped.java index 50b06f3..fcc3067 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTyped.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTyped.java @@ -1,7 +1,17 @@ package fr.pandacube.lib.reflect.wrapper; +/** + * Superclass of all reflect wrapper objects which wrapped objet type is statically known. + * @param the type (or supertype) of the wrapped object. + */ public abstract class ReflectWrapperTyped extends ReflectWrapper implements ReflectWrapperTypedI { + /** + * 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) { super(obj); } diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTypedI.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTypedI.java index 186e879..d043ef1 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTypedI.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/ReflectWrapperTypedI.java @@ -1,5 +1,9 @@ package fr.pandacube.lib.reflect.wrapper; +/** + * Interface implemented by all reflect wrapper objects which wrapped objet type is statically known. + * @param the type (or supertype) of the wrapped object. + */ public interface ReflectWrapperTypedI extends ReflectWrapperI { @SuppressWarnings("unchecked") @Override