From 2a305b1c55d4128fce10a069ab3e30afd2a4dde8 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 27 Aug 2016 22:01:17 +0100 Subject: [PATCH] More progress towards 1.6: add transient permissions --- .../java/me/lucko/luckperms/api/Node.java | 26 + .../lucko/luckperms/api/PermissionHolder.java | 91 ++-- .../java/me/lucko/luckperms/api/Tristate.java | 47 ++ .../luckperms/api/vault/VaultChatHook.java | 8 +- .../internal/PermissionHolderLink.java | 44 +- .../group/subcommands/GroupListNodes.java | 4 +- .../user/subcommands/UserListNodes.java | 3 +- .../luckperms/core/PermissionHolder.java | 457 +++++++++++------- .../java/me/lucko/luckperms/groups/Group.java | 2 +- .../storage/methods/FlatfileDatastore.java | 11 +- .../storage/methods/MongoDBDatastore.java | 7 +- .../java/me/lucko/luckperms/users/User.java | 2 +- .../java/me/lucko/luckperms/utils/Node.java | 68 ++- .../me/lucko/luckperms/SpongeCommand.java | 16 +- .../me/lucko/luckperms/SpongeListener.java | 8 +- .../service/collections/GroupCollection.java | 5 + .../service/collections/UserCollection.java | 5 + 17 files changed, 560 insertions(+), 244 deletions(-) create mode 100644 api/src/main/java/me/lucko/luckperms/api/Tristate.java diff --git a/api/src/main/java/me/lucko/luckperms/api/Node.java b/api/src/main/java/me/lucko/luckperms/api/Node.java index c7cfd3053..ad9b2a573 100644 --- a/api/src/main/java/me/lucko/luckperms/api/Node.java +++ b/api/src/main/java/me/lucko/luckperms/api/Node.java @@ -44,11 +44,23 @@ public interface Node extends Map.Entry { @Override Boolean getValue(); + /** + * @return the value of this node as a Tristate + */ + Tristate getTristate(); + /** * @return true if the node is negated */ boolean isNegated(); + /** + * If this node is set to override explicitly. + * This value does not persist across saves, and is therefore only useful for transient nodes + * @return true if this node is set to override explicitly + */ + boolean isOverride(); + /** * Gets the server this node applies on, if the node is server specific * @return an {@link Optional} containing the server, if one is defined @@ -189,6 +201,13 @@ public interface Node extends Map.Entry { */ int getWildcardLevel(); + /** + * Similar to {@link #equals(Object)}, except doesn't take note of the value + * @param node the other node + * @return true if the two nodes are almost equal + */ + boolean equalsIgnoringValue(Node node); + /** * Similar to {@link #equals(Object)}, except doesn't take note of the expiry time or value * @param node the other node @@ -199,6 +218,13 @@ public interface Node extends Map.Entry { interface Builder { Builder setNegated(boolean negated); Builder setValue(boolean value); + + /** + * Warning: this value does not persist, and disappears when the holder is re-loaded. + * It is therefore only useful for transient nodes. + */ + Builder setOverride(boolean override); + Builder setExpiry(long expireAt); Builder setWorld(String world); Builder setServer(String server) throws IllegalArgumentException; diff --git a/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java b/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java index 7ec60189b..1abb8b013 100644 --- a/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java +++ b/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java @@ -28,6 +28,7 @@ import me.lucko.luckperms.exceptions.ObjectLacksException; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; /** * Wrapper interface for internal PermissionHolder (object/group) instances @@ -43,10 +44,25 @@ public interface PermissionHolder { /** * Gets an immutable Set of the objects permission nodes - * @return an immutable set of permissions + * @return an immutable set of permissions in priority order * @since 1.6 */ - Set getPermissionNodes(); + SortedSet getPermissions(); + + /** + * Similar to {@link #getPermissions()}, except excluding transient permissions + * @return a set of nodes + * @since 1.6 + */ + Set getEnduringPermissions(); + + /** + * Similar to {@link #getPermissions()}, except excluding non-transient permissions + * @return a set of nodes + * @since 1.6 + */ + Set getTransientPermissions(); + /** * Gets an immutable set of the nodes that this object has and inherits @@ -58,7 +74,7 @@ public interface PermissionHolder { /** * Gets an immutable Map of the objects permission nodes * @return an immutable map of permissions - * @deprecated in favour of {@link #getPermissionNodes()} + * @deprecated in favour of {@link #getPermissions()} */ @Deprecated Map getNodes(); @@ -66,11 +82,20 @@ public interface PermissionHolder { /** * Checks to see if the object has a certain permission * @param node the node to check for - * @return true if the object has the permission + * @return a Tristate for the holders permission status for the node * @throws NullPointerException if the node is null * @since 1.6 */ - boolean hasPermission(Node node); + Tristate hasPermission(Node node); + + /** + * Checks to see if the object has a certain permission + * @param node the node to check for + * @return a Tristate for the holders permission status for the node + * @throws NullPointerException if the node is null + * @since 1.6 + */ + Tristate hasTransientPermission(Node node); /** * Checks to see if the object has a certain permission @@ -79,9 +104,7 @@ public interface PermissionHolder { * @return true if the object has the permission * @throws NullPointerException if the node is null * @throws IllegalArgumentException if the node is invalid - * @deprecated in favour of {@link #hasPermission(Node)} */ - @Deprecated boolean hasPermission(String node, boolean b); /** @@ -92,9 +115,7 @@ public interface PermissionHolder { * @return true if the object has the permission * @throws NullPointerException if the node or server is null * @throws IllegalArgumentException if the node or server is invalid - * @deprecated in favour of {@link #hasPermission(Node)} */ - @Deprecated boolean hasPermission(String node, boolean b, String server); /** @@ -106,9 +127,7 @@ public interface PermissionHolder { * @return true if the object has the permission * @throws NullPointerException if the node, server or world is null * @throws IllegalArgumentException if the node, server or world is invalid - * @deprecated in favour of {@link #hasPermission(Node)} */ - @Deprecated boolean hasPermission(String node, boolean b, String server, String world); /** @@ -119,9 +138,7 @@ public interface PermissionHolder { * @return true if the object has the permission * @throws NullPointerException if the node is null * @throws IllegalArgumentException if the node is invalid - * @deprecated in favour of {@link #hasPermission(Node)} */ - @Deprecated boolean hasPermission(String node, boolean b, boolean temporary); /** @@ -133,9 +150,7 @@ public interface PermissionHolder { * @return true if the object has the permission * @throws NullPointerException if the node or server is null * @throws IllegalArgumentException if the node or server is invalid - * @deprecated in favour of {@link #hasPermission(Node)} */ - @Deprecated boolean hasPermission(String node, boolean b, String server, boolean temporary); /** @@ -148,19 +163,17 @@ public interface PermissionHolder { * @return true if the object has the permission * @throws NullPointerException if the node, server or world is null * @throws IllegalArgumentException if the node, server or world is invalid - * @deprecated in favour of {@link #hasPermission(Node)} */ - @Deprecated boolean hasPermission(String node, boolean b, String server, String world, boolean temporary); /** * Cheks to see if the object inherits a certain permission * @param node the node to check for - * @return true if the object inherits the permission + * @return a Tristate for the holders inheritance status for the node * @throws NullPointerException if the node is null * @since 1.6 */ - boolean inheritsPermission(Node node); + Tristate inheritsPermission(Node node); /** * Checks to see if the object inherits a certain permission @@ -169,9 +182,7 @@ public interface PermissionHolder { * @return true if the object inherits the permission * @throws NullPointerException if the node is null * @throws IllegalArgumentException if the node is invalid - * @deprecated in favour of {@link #inheritsPermission(Node)} */ - @Deprecated boolean inheritsPermission(String node, boolean b); /** @@ -182,9 +193,7 @@ public interface PermissionHolder { * @return true if the object inherits the permission * @throws NullPointerException if the node or server is null * @throws IllegalArgumentException if the node or server is invalid - * @deprecated in favour of {@link #inheritsPermission(Node)} */ - @Deprecated boolean inheritsPermission(String node, boolean b, String server); /** @@ -196,9 +205,7 @@ public interface PermissionHolder { * @return true if the object inherits the permission * @throws NullPointerException if the node, server or world is null * @throws IllegalArgumentException if the node server or world is invalid - * @deprecated in favour of {@link #inheritsPermission(Node)} */ - @Deprecated boolean inheritsPermission(String node, boolean b, String server, String world); /** @@ -209,9 +216,7 @@ public interface PermissionHolder { * @return true if the object inherits the permission * @throws NullPointerException if the node is null * @throws IllegalArgumentException if the node is invalid - * @deprecated in favour of {@link #inheritsPermission(Node)} */ - @Deprecated boolean inheritsPermission(String node, boolean b, boolean temporary); /** @@ -223,9 +228,7 @@ public interface PermissionHolder { * @return true if the object inherits the permission * @throws NullPointerException if the node or server is null * @throws IllegalArgumentException if the node or server is invalid - * @deprecated in favour of {@link #inheritsPermission(Node)} */ - @Deprecated boolean inheritsPermission(String node, boolean b, String server, boolean temporary); /** @@ -238,9 +241,7 @@ public interface PermissionHolder { * @return true if the object inherits the permission * @throws NullPointerException if the node, server or world is null * @throws IllegalArgumentException if the node, server or world if invalid - * @deprecated in favour of {@link #inheritsPermission(Node)} */ - @Deprecated boolean inheritsPermission(String node, boolean b, String server, String world, boolean temporary); /** @@ -252,6 +253,25 @@ public interface PermissionHolder { */ void setPermission(Node node) throws ObjectAlreadyHasException; + /** + * Sets a transient permission for the object + * + *

A transient node is a permission that does not persist. + * Whenever a user logs out of the server, or the server restarts, this permission will disappear. + * It is never saved to the datastore, and therefore will not apply on other servers. + * + * This is useful if you want to temporarily set a permission for a user while they're online, but don't + * want it to persist, and have to worry about removing it when they log out. + * + * For unsetting a transient permission, see {@link #unsetTransientPermission(Node)} + * + * @param node The node to be set + * @throws ObjectAlreadyHasException if the object already has the permission + * @throws NullPointerException if the node is null + * @since 1.6 + */ + void setTransientPermission(Node node) throws ObjectAlreadyHasException; + /** * Sets a permission for the object * @param node The node to be set @@ -342,6 +362,15 @@ public interface PermissionHolder { */ void unsetPermission(Node node) throws ObjectLacksException; + /** + * Unsets a transient permission for the object + * @param node The node to be unset + * @throws ObjectLacksException if the node wasn't already set + * @throws NullPointerException if the node is null + * @since 1.6 + */ + void unsetTransientPermission(Node node) throws ObjectLacksException; + /** * Unsets a permission for the object * @param node The node to be unset diff --git a/api/src/main/java/me/lucko/luckperms/api/Tristate.java b/api/src/main/java/me/lucko/luckperms/api/Tristate.java new file mode 100644 index 000000000..02f2d1bbb --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/Tristate.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.api; + +/** + * Represents a permission value + */ +public enum Tristate { + + TRUE(true), + FALSE(false), + UNDEFINED(false); + + private final boolean booleanValue; + + Tristate(boolean booleanValue) { + this.booleanValue = booleanValue; + } + + public boolean asBoolean() { + return booleanValue; + } + + public static Tristate fromBoolean(boolean b) { + return b ? TRUE : FALSE; + } +} diff --git a/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java b/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java index cb03ebf5f..45613a75b 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java @@ -103,7 +103,7 @@ class VaultChatHook extends Chat { if (node.equals("")) return defaultValue; node = escapeCharacters(node); - for (Map.Entry e : holder.getPermissions("global", world, null, true, Collections.emptyList(), false).entrySet()) { + for (Map.Entry e : holder.exportNodes("global", world, null, true, false, Collections.emptyList()).entrySet()) { if (!e.getValue()) continue; String[] parts = Patterns.DOT.split(e.getKey(), 3); @@ -132,7 +132,7 @@ class VaultChatHook extends Chat { if (node.equals("")) return defaultValue; node = escapeCharacters(node); - for (Map.Entry e : holder.getPermissions("global", world, null, true, Collections.emptyList(), false).entrySet()) { + for (Map.Entry e : holder.exportNodes("global", world, null, true, false, Collections.emptyList()).entrySet()) { if (!e.getValue()) continue; String[] parts = Patterns.DOT.split(e.getKey(), 3); @@ -161,7 +161,7 @@ class VaultChatHook extends Chat { if (node.equals("")) return defaultValue; node = escapeCharacters(node); - for (Map.Entry e : holder.getPermissions("global", world, null, true, Collections.emptyList(), false).entrySet()) { + for (Map.Entry e : holder.exportNodes("global", world, null, true, false, Collections.emptyList()).entrySet()) { if (!e.getValue()) continue; String[] parts = Patterns.DOT.split(e.getKey(), 3); @@ -190,7 +190,7 @@ class VaultChatHook extends Chat { if (node.equals("")) return defaultValue; node = escapeCharacters(node); - for (Map.Entry e : holder.getPermissions("global", world, null, true, Collections.emptyList(), false).entrySet()) { + for (Map.Entry e : holder.exportNodes("global", world, null, true, false, Collections.emptyList()).entrySet()) { if (!e.getValue()) continue; String[] parts = Patterns.DOT.split(e.getKey(), 3); diff --git a/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java b/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java index 16d873dd2..4bb2f96a0 100644 --- a/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java +++ b/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java @@ -26,18 +26,19 @@ import lombok.AllArgsConstructor; import lombok.NonNull; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.PermissionHolder; +import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; import java.util.*; import static me.lucko.luckperms.api.implementation.internal.Utils.*; -import static me.lucko.luckperms.core.PermissionHolder.convertToLegacy; +import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy; /** * Provides a link between {@link PermissionHolder} and {@link me.lucko.luckperms.core.PermissionHolder} */ -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "deprecation"}) @AllArgsConstructor public class PermissionHolderLink implements PermissionHolder { @@ -50,10 +51,20 @@ public class PermissionHolderLink implements PermissionHolder { } @Override - public Set getPermissionNodes() { + public SortedSet getPermissions() { + return Collections.unmodifiableSortedSet(master.getPermissions()); + } + + @Override + public Set getEnduringPermissions() { return Collections.unmodifiableSet(master.getNodes()); } + @Override + public Set getTransientPermissions() { + return Collections.unmodifiableSet(master.getTransientNodes()); + } + @Override public Set getAllNodes() { return Collections.unmodifiableSet(master.getAllNodes(null)); @@ -61,12 +72,17 @@ public class PermissionHolderLink implements PermissionHolder { @Override public Map getNodes() { - return convertToLegacy(master.getNodes()); + return exportToLegacy(master.getNodes()); } @Override - public boolean hasPermission(@NonNull Node node) { - return master.hasPermission(node); + public Tristate hasPermission(@NonNull Node node) { + return master.hasPermission(node, false); + } + + @Override + public Tristate hasTransientPermission(@NonNull Node node) { + return master.hasPermission(node, true); } @Override @@ -100,7 +116,7 @@ public class PermissionHolderLink implements PermissionHolder { } @Override - public boolean inheritsPermission(@NonNull Node node) { + public Tristate inheritsPermission(@NonNull Node node) { return master.inheritsPermission(node); } @@ -139,6 +155,11 @@ public class PermissionHolderLink implements PermissionHolder { master.setPermission(node); } + @Override + public void setTransientPermission(@NonNull Node node) throws ObjectAlreadyHasException { + master.setTransientPermission(node); + } + @Override public void setPermission(@NonNull String node, @NonNull boolean value) throws ObjectAlreadyHasException { master.setPermission(checkNode(node), value); @@ -174,6 +195,11 @@ public class PermissionHolderLink implements PermissionHolder { master.unsetPermission(node); } + @Override + public void unsetTransientPermission(@NonNull Node node) throws ObjectLacksException { + master.unsetTransientPermission(node); + } + @Override public void unsetPermission(@NonNull String node, @NonNull boolean temporary) throws ObjectLacksException { master.unsetPermission(checkNode(node), temporary); @@ -226,7 +252,7 @@ public class PermissionHolderLink implements PermissionHolder { @Override public Map getPermissions(String server, String world, Map extraContext, boolean includeGlobal, List possibleNodes, boolean applyGroups) { - return master.getPermissions(server, world, extraContext, includeGlobal, possibleNodes, applyGroups); + return master.exportNodes(server, world, extraContext, includeGlobal, applyGroups, possibleNodes); } @Override @@ -247,7 +273,7 @@ public class PermissionHolderLink implements PermissionHolder { @Override public Map getPermanentNodes() { - return convertToLegacy(master.getPermanentNodes()); + return exportToLegacy(master.getPermanentNodes()); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupListNodes.java b/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupListNodes.java index f4137543a..5efcffbf3 100644 --- a/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupListNodes.java +++ b/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupListNodes.java @@ -30,7 +30,7 @@ import me.lucko.luckperms.groups.Group; import java.util.List; -import static me.lucko.luckperms.core.PermissionHolder.convertToLegacy; +import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy; public class GroupListNodes extends SubCommand { public GroupListNodes() { @@ -40,7 +40,7 @@ public class GroupListNodes extends SubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group group, List args, String label) { - Message.LISTNODES.send(sender, group.getName(), Util.permNodesToString(convertToLegacy(group.getPermanentNodes()))); + Message.LISTNODES.send(sender, group.getName(), Util.permNodesToString(exportToLegacy(group.getPermanentNodes()))); Message.LISTNODES_TEMP.send(sender, group.getName(), Util.tempNodesToString(group.getTemporaryNodesLegacy())); return CommandResult.SUCCESS; } diff --git a/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserListNodes.java b/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserListNodes.java index 80a15847d..ef69bd350 100644 --- a/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserListNodes.java +++ b/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserListNodes.java @@ -26,7 +26,6 @@ import me.lucko.luckperms.LuckPermsPlugin; import me.lucko.luckperms.commands.*; import me.lucko.luckperms.constants.Message; import me.lucko.luckperms.constants.Permission; -import me.lucko.luckperms.core.PermissionHolder; import me.lucko.luckperms.users.User; import java.util.List; @@ -41,7 +40,7 @@ public class UserListNodes extends SubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, User user, List args, String label) { - Message.LISTNODES.send(sender, user.getName(), Util.permNodesToString(convertToLegacy(user.getPermanentNodes()))); + Message.LISTNODES.send(sender, user.getName(), Util.permNodesToString(exportToLegacy(user.getPermanentNodes()))); Message.LISTNODES_TEMP.send(sender, user.getName(), Util.tempNodesToString(user.getTemporaryNodesLegacy())); return CommandResult.SUCCESS; } diff --git a/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java index c652be3eb..6035180d9 100644 --- a/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java @@ -27,6 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import me.lucko.luckperms.LuckPermsPlugin; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.event.events.GroupRemoveEvent; import me.lucko.luckperms.api.event.events.PermissionNodeExpireEvent; import me.lucko.luckperms.api.event.events.PermissionNodeSetEvent; @@ -38,6 +39,7 @@ import me.lucko.luckperms.groups.Group; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; import java.util.stream.Collectors; /** @@ -66,14 +68,195 @@ public abstract class PermissionHolder { @Getter private Set nodes = ConcurrentHashMap.newKeySet(); + /** + * The user/group's transient permissions + */ + @Getter + private Set transientNodes = ConcurrentHashMap.newKeySet(); + + /** + * Returns a Set of nodes in priority order + * @return the holders transient and permanent nodes + */ + public SortedSet getPermissions() { + // Returns no duplicate nodes. as in, nodes with the same value. + + TreeSet permissions = new TreeSet<>(PRIORITY_COMPARATOR); + permissions.addAll(nodes); + permissions.addAll(transientNodes); + + Iterator iterator = permissions.descendingIterator(); // TODO check this + while (iterator.hasNext()) { + Node entry = iterator.next(); + + permissions.stream() + .filter(entry::equalsIgnoringValue) // The node appears again at a higher priority + .forEachOrdered(other -> iterator.remove()); // Remove it. + } + + return permissions; + } + + /** + * Removes temporary permissions that have expired + * @return true if permissions had expired and were removed + */ + public boolean auditTemporaryPermissions() { + boolean work = false; + + Iterator iterator = nodes.iterator(); + while (iterator.hasNext()) { + Node element = iterator.next(); + if (element.hasExpired()) { + iterator.remove(); + + work = true; + plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(this), element)); + } + } + + Iterator iterator2 = transientNodes.iterator(); + while (iterator2.hasNext()) { + Node element = iterator2.next(); + if (element.hasExpired()) { + iterator2.remove(); + + work = true; + plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(this), element)); + } + } + + return work; + } + + /** + * Gets all of the nodes that this holder has and inherits + * @param excludedGroups a list of groups to exclude + * @return a set of nodes + */ + public SortedSet getAllNodes(List excludedGroups) { + SortedSet all = new ConcurrentSkipListSet<>(PRIORITY_COMPARATOR); + all.addAll(getPermissions()); + + if (excludedGroups == null) { + excludedGroups = new ArrayList<>(); + } + + excludedGroups.add(getObjectName().toLowerCase()); + + Set parents = getPermissions().stream() + .filter(Node::isGroupNode) + .map(Node::getGroupName) + .collect(Collectors.toSet()); + + for (String parent : parents) { + Group group = plugin.getGroupManager().get(parent); + if (group == null) { + continue; + } + + if (excludedGroups.contains(group.getObjectName())) { + continue; + } + + inherited: + for (Node inherited : group.getAllNodes(excludedGroups)) { + for (Node existing : all) { + if (existing.almostEquals(inherited)) { + continue inherited; + } + } + + all.add(inherited); + } + } + + return all; + } + + /** + * Gets all of the nodes that this holder has (and inherits), given the context + * @param server the server + * @param world the world + * @param extraContext any extra context to filter by + * @param includeGlobal whether to include global nodes + * @param applyGroups if inherited group permissions should be included + * @return a map of permissions + */ + public Set getAllNodesFiltered(String server, String world, Map extraContext, boolean includeGlobal, boolean applyGroups) { + Set perms = ConcurrentHashMap.newKeySet(); + SortedSet allNodes; + + if (applyGroups) { + allNodes = sort(getAllNodes(null), true); + } else { + allNodes = sort(getPermissions(), true); + } + + for (Node node : allNodes) { + if (!node.shouldApplyOnServer(server, includeGlobal, plugin.getConfiguration().getApplyRegex())) { + continue; + } + + if (!node.shouldApplyOnWorld(world, includeGlobal, plugin.getConfiguration().getApplyRegex())) { + continue; + } + + if (!node.shouldApplyWithContext(extraContext)) { + continue; + } + + perms.add(node); + } + + return perms; + } + + /** + * Converts the output of {@link #getAllNodesFiltered(String, String, Map, boolean, boolean)}, and expands wildcards/regex/shorthand perms + * @param server the server + * @param world the world + * @param extraContext any extra context to filter by + * @param includeGlobal whether to include global nodes + * @param applyGroups if inherited group permissions should be included + * @param possibleNodes a list of possible nodes for wildcards and regex permissions + * @return a map of permissions + */ + public Map exportNodes(String server, String world, Map extraContext, boolean includeGlobal, boolean applyGroups, List possibleNodes) { + Map perms = new HashMap<>(); + + for (Node node : getAllNodesFiltered(server, world, extraContext, includeGlobal, applyGroups)) { + perms.put(node.getPermission(), node.getValue()); + + if (plugin.getConfiguration().getApplyShorthand()) { + node.resolveShorthand().stream() + .filter(s -> !perms.containsKey(s)) + .forEach(s -> perms.put(s, node.getValue())); + } + + if (plugin.getConfiguration().getApplyWildcards()) { + node.resolveWildcard(possibleNodes).stream() + .filter(s -> !perms.containsKey(s)) + .forEach(s -> perms.put(s, node.getValue())); + } + } + + return Collections.unmodifiableMap(perms); + } + public void setNodes(Set nodes) { this.nodes.clear(); this.nodes.addAll(nodes); auditTemporaryPermissions(); } - @Deprecated - public static Map convertToLegacy(Set nodes) { + public void setTransiestNodes(Set nodes) { + this.transientNodes.clear(); + this.transientNodes.addAll(nodes); + auditTemporaryPermissions(); + } + + public static Map exportToLegacy(Set nodes) { Map m = new HashMap<>(); for (Node node : nodes) { m.put(node.toSerializedNode(), node.getValue()); @@ -81,6 +264,7 @@ public abstract class PermissionHolder { return Collections.unmodifiableMap(m); } + // Convenience method private static Node.Builder buildNode(String permission) { return new me.lucko.luckperms.utils.Node.Builder(permission); } @@ -96,84 +280,94 @@ public abstract class PermissionHolder { auditTemporaryPermissions(); } - private static boolean hasPermission(Set toQuery, Node node) { + private static Tristate hasPermission(Set toQuery, Node node) { for (Node n : toQuery) { - if (n.almostEquals(node)) { - return true; + if (n.equalsIgnoringValue(node)) { + n.getTristate(); } } - return false; + return Tristate.UNDEFINED; } /** * Check if the holder has a permission node * @param node the node to check - * @return true if the holder has the node + * @param t whether to check transient nodes + * @return a tristate */ - public boolean hasPermission(Node node) { - return hasPermission(this.nodes, node); + public Tristate hasPermission(Node node, boolean t) { + return hasPermission(t ? transientNodes : nodes, node); + } + + public Tristate hasPermission(Node node) { + return hasPermission(node, false); } public boolean hasPermission(String node, boolean b) { - return hasPermission(buildNode(node).setValue(b).build()); + return hasPermission(buildNode(node).setValue(b).build()).asBoolean() == b; } public boolean hasPermission(String node, boolean b, String server) { - return hasPermission(buildNode(node).setValue(b).setServer(server).build()); + return hasPermission(buildNode(node).setValue(b).setServer(server).build()).asBoolean() == b; } public boolean hasPermission(String node, boolean b, String server, String world) { - return hasPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).build()); + return hasPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).build()).asBoolean() == b; } public boolean hasPermission(String node, boolean b, boolean temporary) { - return hasPermission(buildNode(node).setValue(b).setExpiry(temporary ? 10L : 0L).build()); + return hasPermission(buildNode(node).setValue(b).setExpiry(temporary ? 10L : 0L).build()).asBoolean() == b; } public boolean hasPermission(String node, boolean b, String server, boolean temporary) { - return hasPermission(buildNode(node).setValue(b).setServer(server).setExpiry(temporary ? 10L : 0L).build()); + return hasPermission(buildNode(node).setValue(b).setServer(server).setExpiry(temporary ? 10L : 0L).build()).asBoolean() == b; } public boolean hasPermission(String node, boolean b, String server, String world, boolean temporary) { - return hasPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).setExpiry(temporary ? 10L : 0L).build()); + return hasPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).setExpiry(temporary ? 10L : 0L).build()).asBoolean() == b; } /** * Check if the holder inherits a node * @param node the node to check - * @return true if the holder inherits the node + * @return a tristate */ - public boolean inheritsPermission(Node node) { + public Tristate inheritsPermission(Node node) { return hasPermission(getAllNodes(null), node); } public boolean inheritsPermission(String node, boolean b) { - return inheritsPermission(buildNode(node).setValue(b).build()); + return inheritsPermission(buildNode(node).setValue(b).build()).asBoolean() == b; } public boolean inheritsPermission(String node, boolean b, String server) { - return inheritsPermission(buildNode(node).setValue(b).setServer(server).build()); + return inheritsPermission(buildNode(node).setValue(b).setServer(server).build()).asBoolean() == b; } public boolean inheritsPermission(String node, boolean b, String server, String world) { - return inheritsPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).build()); + return inheritsPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).build()).asBoolean() == b; } public boolean inheritsPermission(String node, boolean b, boolean temporary) { - return inheritsPermission(buildNode(node).setValue(b).setExpiry(temporary ? 10L : 0L).build()); + return inheritsPermission(buildNode(node).setValue(b).setExpiry(temporary ? 10L : 0L).build()).asBoolean() == b; } public boolean inheritsPermission(String node, boolean b, String server, boolean temporary) { - return inheritsPermission(buildNode(node).setValue(b).setServer(server).setExpiry(temporary ? 10L : 0L).build()); + return inheritsPermission(buildNode(node).setValue(b).setServer(server).setExpiry(temporary ? 10L : 0L).build()).asBoolean() == b; } public boolean inheritsPermission(String node, boolean b, String server, String world, boolean temporary) { - return inheritsPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).setExpiry(temporary ? 10L : 0L).build()); + return inheritsPermission(buildNode(node).setValue(b).setServer(server).setWorld(world).setExpiry(temporary ? 10L : 0L).build()).asBoolean() == b; } + /** + * Sets a permission node + * @param node the node to set + * @throws ObjectAlreadyHasException if the holder has this permission already + */ public void setPermission(Node node) throws ObjectAlreadyHasException { - if (hasPermission(node)) { + if (hasPermission(node, false) == Tristate.TRUE) { throw new ObjectAlreadyHasException(); } @@ -181,6 +375,20 @@ public abstract class PermissionHolder { plugin.getApiProvider().fireEventAsync(new PermissionNodeSetEvent(new PermissionHolderLink(this), node)); } + /** + * Sets a transient permission node + * @param node the node to set + * @throws ObjectAlreadyHasException if the holder has this permission already + */ + public void setTransientPermission(Node node) throws ObjectAlreadyHasException { + if (hasPermission(node, true) == Tristate.TRUE) { + throw new ObjectAlreadyHasException(); + } + + transientNodes.add(node); + plugin.getApiProvider().fireEventAsync(new PermissionNodeSetEvent(new PermissionHolderLink(this), node)); + } + public void setPermission(String node, boolean value) throws ObjectAlreadyHasException { setPermission(buildNode(node).setValue(value).build()); } @@ -211,7 +419,7 @@ public abstract class PermissionHolder { * @throws ObjectLacksException if the holder doesn't have this node already */ public void unsetPermission(Node node) throws ObjectLacksException { - if (!hasPermission(node)) { + if (hasPermission(node, false) == Tristate.FALSE) { throw new ObjectLacksException(); } @@ -225,6 +433,26 @@ public abstract class PermissionHolder { } } + /** + * Unsets a transient permission node + * @param node the node to unset + * @throws ObjectLacksException if the holder doesn't have this node already + */ + public void unsetTransientPermission(Node node) throws ObjectLacksException { + if (hasPermission(node, true) == Tristate.FALSE) { + throw new ObjectLacksException(); + } + + transientNodes.remove(node); + + if (node.isGroupNode()) { + plugin.getApiProvider().fireEventAsync(new GroupRemoveEvent(new PermissionHolderLink(this), + node.getGroupName(), node.getServer().orElse(null), node.getWorld().orElse(null), node.isTemporary())); + } else { + plugin.getApiProvider().fireEventAsync(new PermissionNodeUnsetEvent(new PermissionHolderLink(this), node)); + } + } + public void unsetPermission(String node, boolean temporary) throws ObjectLacksException { unsetPermission(buildNode(node).setExpiry(temporary ? 10L : 0L).build()); } @@ -253,7 +481,7 @@ public abstract class PermissionHolder { * @return The temporary nodes held by the holder */ public Set getTemporaryNodes() { - return nodes.stream().filter(Node::isTemporary).collect(Collectors.toSet()); + return getPermissions().stream().filter(Node::isTemporary).collect(Collectors.toSet()); } @Deprecated @@ -271,84 +499,18 @@ public abstract class PermissionHolder { * @return The permanent nodes held by the holder */ public Set getPermanentNodes() { - return nodes.stream().filter(Node::isPermanent).collect(Collectors.toSet()); - } - - /** - * Removes temporary permissions that have expired - * @return true if permissions had expired and were removed - */ - public boolean auditTemporaryPermissions() { - boolean work = false; - Iterator iterator = nodes.iterator(); - - while (iterator.hasNext()) { - Node element = iterator.next(); - if (element.hasExpired()) { - iterator.remove(); - - work = true; - plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(this), element)); - } - } - - return work; - } - - /** - * Gets all of the nodes that this holder has and inherits - * @param excludedGroups a list of groups to exclude - * @return a set of nodes - */ - public Set getAllNodes(List excludedGroups) { - Set all = ConcurrentHashMap.newKeySet(); - all.addAll(nodes); - - if (excludedGroups == null) { - excludedGroups = new ArrayList<>(); - } - - excludedGroups.add(getObjectName().toLowerCase()); - - Set parents = nodes.stream() - .filter(Node::isGroupNode) - .map(Node::getGroupName) - .collect(Collectors.toSet()); - - for (String parent : parents) { - Group group = plugin.getGroupManager().get(parent); - if (group == null) { - continue; - } - - if (excludedGroups.contains(group.getObjectName())) { - continue; - } - - inherited: - for (Node inherited : group.getAllNodes(excludedGroups)) { - for (Node existing : all) { - if (existing.almostEquals(inherited)) { - continue inherited; - } - } - - all.add(inherited); - } - } - - return all; + return getPermissions().stream().filter(Node::isPermanent).collect(Collectors.toSet()); } /* * Don't use these methods, only here for compat reasons */ public Map getLocalPermissions(String server, String world, List excludedGroups, List possibleNodes) { - return getPermissions(server, world, null, plugin.getConfiguration().getIncludeGlobalPerms(), possibleNodes, true); + return exportNodes(server, world, null, plugin.getConfiguration().getIncludeGlobalPerms(), true, possibleNodes); } public Map getLocalPermissions(String server, String world, List excludedGroups) { - return getPermissions(server, world, null, plugin.getConfiguration().getIncludeGlobalPerms(), null, true); + return exportNodes(server, world, null, plugin.getConfiguration().getIncludeGlobalPerms(), true, null); } public Map getLocalPermissions(String server, List excludedGroups, List possibleNodes) { @@ -359,57 +521,6 @@ public abstract class PermissionHolder { return getLocalPermissions(server, null, excludedGroups, null); } - /** - * Convert the holders nodes into a Map of permissions to be applied on the platform - * @param server the server - * @param world the world - * @param extraContext any extra context to filter by - * @param includeGlobal whether to include global nodes - * @param possibleNodes a list of possible permissions for resolving wildcards - * @param applyGroups if inherited group permissions should be included - * @return a map of permissions - */ - public Map getPermissions(String server, String world, Map extraContext, boolean includeGlobal, List possibleNodes, boolean applyGroups) { - Map perms = new HashMap<>(); - SortedSet allNodes; - - if (applyGroups) { - allNodes = sort(getAllNodes(null), true); - } else { - allNodes = sort(nodes, true); - } - - for (Node node : allNodes) { - if (!node.shouldApplyOnServer(server, includeGlobal, plugin.getConfiguration().getApplyRegex())) { - continue; - } - - if (!node.shouldApplyOnWorld(world, includeGlobal, plugin.getConfiguration().getApplyRegex())) { - continue; - } - - if (!node.shouldApplyWithContext(extraContext)) { - continue; - } - - perms.put(node.getPermission(), node.getValue()); - - if (plugin.getConfiguration().getApplyShorthand()) { - node.resolveShorthand().stream() - .filter(s -> !perms.containsKey(s)) - .forEach(s -> perms.put(s, node.getValue())); - } - - if (plugin.getConfiguration().getApplyWildcards()) { - node.resolveWildcard(possibleNodes).stream() - .filter(s -> !perms.containsKey(s)) - .forEach(s -> perms.put(s, node.getValue())); - } - } - - return perms; - } - public static SortedSet sort(Set toSort, boolean reversed) { TreeSet set = new TreeSet<>(reversed ? PRIORITY_COMPARATOR.reversed() : PRIORITY_COMPARATOR); set.addAll(toSort); @@ -421,41 +532,35 @@ public abstract class PermissionHolder { @Override public int compare(Node o1, Node o2) { - if (takesPriority(o1, o2)) { - return 1; + if (o1.isOverride() != o2.isOverride()) { + return o1.isOverride() ? 1 : -1; } - if (takesPriority(o2, o1)) { - return -1; + if (o1.isServerSpecific() != o2.isServerSpecific()) { + return o1.isServerSpecific() ? 1 : -1; + } + + if (o1.isWorldSpecific() != o2.isWorldSpecific()) { + return o1.isWorldSpecific() ? 1 : -1; + } + + if (o1.isTemporary() != o2.isTemporary()) { + return o1.isTemporary() ? 1 : -1; + } + + if (o1.isWildcard() != o2.isWildcard()) { + return o1.isWildcard() ? 1 : -1; + } + + if (o1.isTemporary()) { + return o1.getSecondsTilExpiry() < o2.getSecondsTilExpiry() ? 1 : -1; + } + + if (o1.isWildcard()) { + return o1.getWildcardLevel() > o2.getWildcardLevel() ? 1 : -1; } return 1; } - - public boolean takesPriority(Node target, Node over) { - if (target.isTemporary() && !over.isTemporary()) { - return true; - } - - if (target.isWorldSpecific() && !over.isWorldSpecific()) { - return true; - } - - if (target.isServerSpecific() && !over.isServerSpecific()) { - return true; - } - - if (!target.isWildcard() && over.isWildcard()) { - return true; - } - - if (target.isWildcard() && over.isWildcard()) { - if (target.getWildcardLevel() > over.getWildcardLevel()) { - return true; - } - } - - return false; - } } } diff --git a/common/src/main/java/me/lucko/luckperms/groups/Group.java b/common/src/main/java/me/lucko/luckperms/groups/Group.java index a613062c0..8d15bd6e3 100644 --- a/common/src/main/java/me/lucko/luckperms/groups/Group.java +++ b/common/src/main/java/me/lucko/luckperms/groups/Group.java @@ -321,7 +321,7 @@ public class Group extends PermissionHolder implements Identifiable { */ private List getGroups(String server, String world, boolean includeGlobal) { // Call super #getPermissions method, and just sort through those - Map perms = getPermissions(server, world, null, includeGlobal, null, true); + Map perms = exportNodes(server, world, null, includeGlobal, true, null); return perms.keySet().stream() .filter(s -> Patterns.GROUP_MATCH.matcher(s).matches()) .map(s -> Patterns.DOT.split(s, 2)[1]) diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java index c20c0b652..d4fd4c853 100644 --- a/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java +++ b/common/src/main/java/me/lucko/luckperms/storage/methods/FlatfileDatastore.java @@ -28,7 +28,6 @@ import lombok.Cleanup; import me.lucko.luckperms.LuckPermsPlugin; import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.constants.Constants; -import me.lucko.luckperms.core.PermissionHolder; import me.lucko.luckperms.data.Log; import me.lucko.luckperms.groups.Group; import me.lucko.luckperms.storage.Datastore; @@ -188,7 +187,7 @@ public class FlatfileDatastore extends Datastore { writer.name("primaryGroup").value(user.getPrimaryGroup()); writer.name("perms"); writer.beginObject(); - for (Map.Entry e : convertToLegacy(user.getNodes()).entrySet()) { + for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) { writer.name(e.getKey()).value(e.getValue().booleanValue()); } writer.endObject(); @@ -229,7 +228,7 @@ public class FlatfileDatastore extends Datastore { writer.name("primaryGroup").value(user.getPrimaryGroup()); writer.name("perms"); writer.beginObject(); - for (Map.Entry e : convertToLegacy(user.getNodes()).entrySet()) { + for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) { writer.name(e.getKey()).value(e.getValue().booleanValue()); } writer.endObject(); @@ -295,7 +294,7 @@ public class FlatfileDatastore extends Datastore { writer.name("primaryGroup").value(user.getPrimaryGroup()); writer.name("perms"); writer.beginObject(); - for (Map.Entry e : convertToLegacy(user.getNodes()).entrySet()) { + for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) { writer.name(e.getKey()).value(e.getValue().booleanValue()); } writer.endObject(); @@ -323,7 +322,7 @@ public class FlatfileDatastore extends Datastore { writer.name("name").value(group.getName()); writer.name("perms"); writer.beginObject(); - for (Map.Entry e : convertToLegacy(group.getNodes()).entrySet()) { + for (Map.Entry e : exportToLegacy(group.getNodes()).entrySet()) { writer.name(e.getKey()).value(e.getValue().booleanValue()); } writer.endObject(); @@ -415,7 +414,7 @@ public class FlatfileDatastore extends Datastore { writer.name("name").value(group.getName()); writer.name("perms"); writer.beginObject(); - for (Map.Entry e : convertToLegacy(group.getNodes()).entrySet()) { + for (Map.Entry e : exportToLegacy(group.getNodes()).entrySet()) { writer.name(e.getKey()).value(e.getValue().booleanValue()); } writer.endObject(); diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/MongoDBDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/MongoDBDatastore.java index f4ea4b62a..f20b32a1e 100644 --- a/common/src/main/java/me/lucko/luckperms/storage/methods/MongoDBDatastore.java +++ b/common/src/main/java/me/lucko/luckperms/storage/methods/MongoDBDatastore.java @@ -31,7 +31,6 @@ import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.InsertOneOptions; import me.lucko.luckperms.LuckPermsPlugin; import me.lucko.luckperms.api.LogEntry; -import me.lucko.luckperms.core.PermissionHolder; import me.lucko.luckperms.data.Log; import me.lucko.luckperms.groups.Group; import me.lucko.luckperms.groups.GroupManager; @@ -46,7 +45,7 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.stream.Collectors; -import static me.lucko.luckperms.core.PermissionHolder.convertToLegacy; +import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy; @SuppressWarnings("unchecked") public class MongoDBDatastore extends Datastore { @@ -430,7 +429,7 @@ public class MongoDBDatastore extends Datastore { .append("primaryGroup", user.getPrimaryGroup()); Document perms = new Document(); - for (Map.Entry e : convert(convertToLegacy(user.getNodes())).entrySet()) { + for (Map.Entry e : convert(exportToLegacy(user.getNodes())).entrySet()) { perms.append(e.getKey(), e.getValue()); } @@ -442,7 +441,7 @@ public class MongoDBDatastore extends Datastore { Document main = new Document("_id", group.getName()); Document perms = new Document(); - for (Map.Entry e : convert(convertToLegacy(group.getNodes())).entrySet()) { + for (Map.Entry e : convert(exportToLegacy(group.getNodes())).entrySet()) { perms.append(e.getKey(), e.getValue()); } diff --git a/common/src/main/java/me/lucko/luckperms/users/User.java b/common/src/main/java/me/lucko/luckperms/users/User.java index 8e57f4175..71239c4ee 100644 --- a/common/src/main/java/me/lucko/luckperms/users/User.java +++ b/common/src/main/java/me/lucko/luckperms/users/User.java @@ -330,7 +330,7 @@ public abstract class User extends PermissionHolder implements Identifiable getGroups(String server, String world, boolean includeGlobal) { // Call super #getPermissions method, and just sort through those - Map perms = getPermissions(server, world, null, includeGlobal, null, true); + Map perms = exportNodes(server, world, null, includeGlobal, true, null); return perms.keySet().stream() .filter(s -> Patterns.GROUP_MATCH.matcher(s).matches()) .map(s -> Patterns.DOT.split(s, 2)[1]) diff --git a/common/src/main/java/me/lucko/luckperms/utils/Node.java b/common/src/main/java/me/lucko/luckperms/utils/Node.java index b7c24e445..5f1316ba6 100644 --- a/common/src/main/java/me/lucko/luckperms/utils/Node.java +++ b/common/src/main/java/me/lucko/luckperms/utils/Node.java @@ -24,6 +24,7 @@ package me.lucko.luckperms.utils; import com.google.common.collect.ImmutableMap; import lombok.*; +import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.constants.Patterns; import java.util.*; @@ -85,6 +86,9 @@ public class Node implements me.lucko.luckperms.api.Node { @Getter private Boolean value; + @Getter + private boolean override; + private String server = null; private String world = null; @@ -101,7 +105,7 @@ public class Node implements me.lucko.luckperms.api.Node { * @param world the world this node applies on * @param extraContexts any additional contexts applying to this node */ - public Node(String permission, boolean value, long expireAt, String server, String world, Map extraContexts) { + public Node(String permission, boolean value, boolean override, long expireAt, String server, String world, Map extraContexts) { if (permission == null || permission.equals("")) { throw new IllegalArgumentException("Empty permission"); } @@ -120,6 +124,7 @@ public class Node implements me.lucko.luckperms.api.Node { this.permission = permission; this.value = value; + this.override = override; this.expireAt = expireAt; this.server = server; this.world = world; @@ -129,6 +134,11 @@ public class Node implements me.lucko.luckperms.api.Node { } } + @Override + public Tristate getTristate() { + return Tristate.fromBoolean(value); + } + public boolean isNegated() { return !value; } @@ -395,6 +405,53 @@ public class Node implements me.lucko.luckperms.api.Node { return (int) getPermission().chars().filter(num -> num == Character.getNumericValue('.')).count(); } + @Override + public boolean equalsIgnoringValue(me.lucko.luckperms.api.Node other) { + if (!other.getPermission().equalsIgnoreCase(this.getPermission())) { + return false; + } + + if (other.isTemporary() != this.isTemporary()) { + return false; + } + + if (this.isTemporary()) { + if (other.getExpiryUnixTime() != this.getExpiryUnixTime()) { + return false; + } + } + + if (other.getServer().isPresent() != this.getServer().isPresent()) { + if (other.getServer().isPresent()) { + if (!other.getServer().get().equalsIgnoreCase(this.getServer().get())) { + return false; + } + } + } else { + return false; + } + + if (other.getWorld().isPresent() != this.getWorld().isPresent()) { + if (other.getWorld().isPresent()) { + if (!other.getWorld().get().equalsIgnoreCase(this.getWorld().get())) { + return false; + } + } + } else { + return false; + } + + if (!other.getExtraContexts().equals(this.getExtraContexts())) { + return false; + } + + if (other.isTemporary() != this.isTemporary()) { + return false; + } + + return true; + } + @Override public boolean almostEquals(me.lucko.luckperms.api.Node other) { if (!other.getPermission().equalsIgnoreCase(this.getPermission())) { @@ -446,6 +503,7 @@ public class Node implements me.lucko.luckperms.api.Node { public static class Builder implements me.lucko.luckperms.api.Node.Builder { private final String permission; private Boolean value = true; + private boolean override = false; private String server = null; private String world = null; private long expireAt = 0L; @@ -488,6 +546,12 @@ public class Node implements me.lucko.luckperms.api.Node { return this; } + @Override + public me.lucko.luckperms.api.Node.Builder setOverride(boolean override) { + this.override = override; + return this; + } + @Override public me.lucko.luckperms.api.Node.Builder setExpiry(long expireAt) { this.expireAt = expireAt; @@ -523,7 +587,7 @@ public class Node implements me.lucko.luckperms.api.Node { @Override public me.lucko.luckperms.api.Node build() { - return new Node(permission, value, expireAt, server, world, extraContexts); + return new Node(permission, value, override, expireAt, server, world, extraContexts); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/SpongeCommand.java b/sponge/src/main/java/me/lucko/luckperms/SpongeCommand.java index 2c4592275..7132d37f9 100644 --- a/sponge/src/main/java/me/lucko/luckperms/SpongeCommand.java +++ b/sponge/src/main/java/me/lucko/luckperms/SpongeCommand.java @@ -30,7 +30,10 @@ import org.spongepowered.api.command.CommandException; import org.spongepowered.api.command.CommandResult; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.text.Text; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -49,6 +52,15 @@ class SpongeCommand extends CommandManager implements CommandCallable { } @Override + public List getSuggestions(CommandSource source, String s, @Nullable Location location) throws CommandException { + List args = new ArrayList<>(Arrays.asList(Patterns.SPACE.split(s))); + if (s.endsWith(" ")) { + args.add(""); + } + + return onTabComplete(SpongeSenderFactory.get().wrap(source), args); + } + public List getSuggestions(CommandSource source, String s) throws CommandException { List args = new ArrayList<>(Arrays.asList(Patterns.SPACE.split(s))); if (s.endsWith(" ")) { @@ -64,12 +76,12 @@ class SpongeCommand extends CommandManager implements CommandCallable { } @Override - public Optional getShortDescription(CommandSource source) { + public Optional getShortDescription(CommandSource source) { return Optional.of(Text.of("LuckPerms main command.")); } @Override - public Optional getHelp(CommandSource source) { + public Optional getHelp(CommandSource source) { return Optional.of(Text.of("Type /perms for help.")); } diff --git a/sponge/src/main/java/me/lucko/luckperms/SpongeListener.java b/sponge/src/main/java/me/lucko/luckperms/SpongeListener.java index 361f432a6..dbc4beab2 100644 --- a/sponge/src/main/java/me/lucko/luckperms/SpongeListener.java +++ b/sponge/src/main/java/me/lucko/luckperms/SpongeListener.java @@ -25,10 +25,7 @@ package me.lucko.luckperms; import me.lucko.luckperms.constants.Message; import me.lucko.luckperms.users.User; import me.lucko.luckperms.utils.AbstractListener; -import org.spongepowered.api.entity.Entity; -import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.entity.DisplaceEntityEvent; import org.spongepowered.api.event.network.ClientConnectionEvent; import org.spongepowered.api.profile.GameProfile; import org.spongepowered.api.text.serializer.TextSerializers; @@ -75,8 +72,10 @@ public class SpongeListener extends AbstractListener { refreshPlayer(e.getTargetEntity().getUniqueId()); } + // TODO Use reflection/any other method to refresh on world change + /* @Listener - public void onPlayerTeleport(DisplaceEntityEvent.Teleport e) { + public void onPlayerTeleport(DisplaceEntityEvent e) { final Entity entity = e.getTargetEntity(); if (!(entity instanceof Player)){ return; @@ -84,6 +83,7 @@ public class SpongeListener extends AbstractListener { refreshPlayer(entity.getUniqueId()); } + */ @Listener public void onClientLeave(ClientConnectionEvent.Disconnect e) { diff --git a/sponge/src/main/java/me/lucko/luckperms/service/collections/GroupCollection.java b/sponge/src/main/java/me/lucko/luckperms/service/collections/GroupCollection.java index ad34154a0..bb6a37d54 100644 --- a/sponge/src/main/java/me/lucko/luckperms/service/collections/GroupCollection.java +++ b/sponge/src/main/java/me/lucko/luckperms/service/collections/GroupCollection.java @@ -83,4 +83,9 @@ public class GroupCollection implements SubjectCollection { .map(sub -> new AbstractMap.SimpleEntry(sub, sub.getPermissionValue(contexts, node).asBoolean())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + + @Override + public Subject getDefaults() { + return null; + } } diff --git a/sponge/src/main/java/me/lucko/luckperms/service/collections/UserCollection.java b/sponge/src/main/java/me/lucko/luckperms/service/collections/UserCollection.java index 5c896c0d3..4109d27cb 100644 --- a/sponge/src/main/java/me/lucko/luckperms/service/collections/UserCollection.java +++ b/sponge/src/main/java/me/lucko/luckperms/service/collections/UserCollection.java @@ -101,4 +101,9 @@ public class UserCollection implements SubjectCollection { .map(sub -> new AbstractMap.SimpleEntry(sub, sub.getPermissionValue(contexts, node).asBoolean())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + + @Override + public Subject getDefaults() { + return null; + } }