From 1137e476dde1151cb27e45c3ec1c94340ec7940b Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 24 Mar 2017 22:18:03 +0000 Subject: [PATCH] Rewrite inheritance resolution implementation --- .../luckperms/api/context/ContextSet.java | 39 + .../luckperms/bukkit/vault/VaultChatHook.java | 10 +- .../bukkit/vault/VaultPermissionHook.java | 10 +- .../delegates/PermissionHolderDelegate.java | 12 +- .../{MetaHolder.java => MetaAccumulator.java} | 6 +- .../luckperms/common/caching/MetaCache.java | 2 +- .../luckperms/common/caching/UserCache.java | 4 +- .../caching/handlers/CachedStateManager.java | 8 +- .../caching/stacking/MetaStackElement.java | 3 +- .../commands/impl/generic/meta/MetaInfo.java | 2 +- .../impl/generic/meta/MetaRemovePrefix.java | 2 +- .../impl/generic/meta/MetaRemoveSuffix.java | 2 +- .../generic/meta/MetaRemoveTempPrefix.java | 2 +- .../generic/meta/MetaRemoveTempSuffix.java | 2 +- .../impl/generic/other/HolderShowTracks.java | 2 +- .../impl/generic/parent/ParentInfo.java | 4 +- .../generic/permission/PermissionInfo.java | 6 +- .../commands/impl/group/GroupBulkChange.java | 2 +- .../commands/impl/group/GroupClone.java | 2 +- .../common/commands/impl/group/GroupInfo.java | 4 +- .../commands/impl/group/GroupRename.java | 2 +- .../commands/impl/group/GroupSetWeight.java | 20 +- .../commands/impl/user/UserBulkChange.java | 2 +- .../common/commands/impl/user/UserDemote.java | 2 +- .../common/commands/impl/user/UserInfo.java | 4 +- .../commands/impl/user/UserPromote.java | 2 +- .../impl/usersbulkedit/BulkEditGroup.java | 2 +- .../usersbulkedit/BulkEditPermission.java | 2 +- .../common/core/ContextSetComparator.java | 115 ++ .../luckperms/common/core/NodeComparator.java | 64 + .../common/core/PriorityComparator.java | 10 +- .../common/core/model/PermissionHolder.java | 1028 +++++++++-------- .../luckperms/common/core/model/User.java | 1 - .../lucko/luckperms/common/data/Exporter.java | 4 +- .../luckperms/common/event/EventFactory.java | 2 +- .../managers/impl/GenericUserManager.java | 4 +- .../AllParentsByWeightHolder.java | 2 +- .../primarygroup/ParentsByWeightHolder.java | 2 +- .../common/storage/backing/JSONBacking.java | 6 +- .../storage/backing/MongoDBBacking.java | 20 +- .../common/storage/backing/SQLBacking.java | 4 +- .../common/storage/backing/YAMLBacking.java | 6 +- .../common/utils/ExtractedContexts.java | 16 +- .../luckperms/common/utils/NodeTools.java | 103 ++ .../luckperms/sponge/model/SpongeGroup.java | 16 +- .../luckperms/sponge/model/SpongeUser.java | 2 +- .../sponge/service/LuckPermsSubjectData.java | 14 +- 47 files changed, 983 insertions(+), 596 deletions(-) rename common/src/main/java/me/lucko/luckperms/common/caching/{MetaHolder.java => MetaAccumulator.java} (96%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/core/ContextSetComparator.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/core/NodeComparator.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/utils/NodeTools.java diff --git a/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java b/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java index 0646e204..14fa9768 100644 --- a/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java +++ b/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java @@ -25,6 +25,7 @@ package me.lucko.luckperms.api.context; import com.google.common.collect.Multimap; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -167,6 +168,16 @@ public interface ContextSet { */ Set getValues(String key); + /** + * Returns any value from this set matching the key, if present. + * + * @param key the key to find values for + * @return an optional containing any match + */ + default Optional getAnyValue(String key) { + return getValues(key).stream().findAny(); + } + /** * Check if thr set contains a given key mapped to a given value * @@ -187,6 +198,34 @@ public interface ContextSet { */ boolean hasIgnoreCase(String key, String value); + /** + * Checks to see if all entries in this context set are also included in another set. + * + * @param other the other set to check + * @return true if all entries in this set are also in the other set + */ + default boolean isSatisfiedBy(ContextSet other) { + if (this.isEmpty()) { + // this is empty, so is therefore always satisfied. + return true; + } else if (other.isEmpty()) { + // this set isn't empty, but the other one is + return false; + } else if (this.size() > other.size()) { + // this set has more unique entries than the other set, so there's no way this can be satisfied. + return false; + } else { + // neither are empty, we need to compare the individual entries + for (Map.Entry pair : toSet()) { + if (!other.has(pair.getKey(), pair.getValue())) { + return false; + } + } + + return true; + } + } + /** * Check if the set is empty * diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java index 57e692f5..fd4dc275 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java @@ -28,7 +28,7 @@ import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.caching.MetaData; import me.lucko.luckperms.api.context.ContextSet; -import me.lucko.luckperms.common.caching.MetaHolder; +import me.lucko.luckperms.common.caching.MetaAccumulator; import me.lucko.luckperms.common.core.NodeFactory; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.PermissionHolder; @@ -99,8 +99,8 @@ public class VaultChatHook extends Chat { holder.removeIf(n -> prefix ? n.isPrefix() : n.isSuffix()); // find the max inherited priority & add 10 - MetaHolder metaHolder = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorld(finalWorld))); - int priority = (prefix ? metaHolder.getPrefixes() : metaHolder.getSuffixes()).keySet().stream() + MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(perms.createContextForWorld(finalWorld))); + int priority = (prefix ? metaAccumulator.getPrefixes() : metaAccumulator.getSuffixes()).keySet().stream() .mapToInt(e -> e).max().orElse(0) + 10; Node.Builder chatMetaNode = NodeFactory.makeChatMetaNode(prefix, priority, value); @@ -158,7 +158,7 @@ public class VaultChatHook extends Chat { perms.log("Getting meta: '" + node + "' for group " + group.getName() + " on world " + world + ", server " + perms.getServer()); - for (Node n : group.getPermissions(true)) { + for (Node n : group.mergePermissionsToList()) { if (!n.getValue()) continue; if (!n.isMeta()) continue; if (!n.shouldApplyOnServer(perms.getServer(), perms.isIncludeGlobal(), false)) continue; @@ -189,7 +189,7 @@ public class VaultChatHook extends Chat { } ExtractedContexts ec = ExtractedContexts.generate(new Contexts(ContextSet.fromMap(context), perms.isIncludeGlobal(), true, true, true, true, false)); - for (Node n : group.getAllNodes(null, ec)) { + for (Node n : group.getAllNodes(ec)) { if (!n.getValue()) continue; if (prefix ? !n.isPrefix() : !n.isSuffix()) continue; if (!n.shouldApplyOnServer(perms.getServer(), perms.isIncludeGlobal(), false)) continue; diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java index b79fd7ad..14546177 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java @@ -26,7 +26,6 @@ import lombok.Getter; import lombok.NonNull; import me.lucko.luckperms.api.Contexts; -import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.caching.PermissionData; @@ -36,6 +35,7 @@ import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.PermissionHolder; import me.lucko.luckperms.common.core.model.User; +import me.lucko.luckperms.common.utils.ExtractedContexts; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; @@ -197,7 +197,7 @@ public class VaultPermissionHook extends Permission { if (group == null) return false; // This is a nasty call. Groups aren't cached. :( - Map permissions = group.exportNodes(createContextForWorld(world), true); + Map permissions = group.exportNodes(ExtractedContexts.generate(createContextForWorld(world)), true); return permissions.containsKey(permission.toLowerCase()) && permissions.get(permission.toLowerCase()); } @@ -234,7 +234,7 @@ public class VaultPermissionHook extends Permission { if (user == null) return false; String w = world; // screw effectively final - return user.getNodes().stream() + return user.getNodes().values().stream() .filter(Node::isGroupNode) .filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) .filter(n -> n.shouldApplyOnWorld(w, true, false)) @@ -303,7 +303,7 @@ public class VaultPermissionHook extends Permission { if (user == null) return new String[0]; String w = world; // screw effectively final - return user.getNodes().stream() + return user.getNodes().values().stream() .filter(Node::isGroupNode) .filter(n -> n.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) .filter(n -> n.shouldApplyOnWorld(w, true, false)) @@ -352,7 +352,7 @@ public class VaultPermissionHook extends Permission { } } else { // we need to check the users permissions only - for (LocalizedNode node : user.getPermissions(true)) { + for (Node node : user.mergePermissionsToList()) { if (!node.getValue()) continue; if (!node.getPermission().toLowerCase().startsWith("vault.primarygroup.")) continue; if (!node.shouldApplyOnServer(getServer(), isIncludeGlobal(), false)) continue; diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/PermissionHolderDelegate.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/PermissionHolderDelegate.java index 28e8aab7..dc9eb7bf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/PermissionHolderDelegate.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/PermissionHolderDelegate.java @@ -60,32 +60,32 @@ public class PermissionHolderDelegate implements PermissionHolder { @Override public SortedSet getPermissions() { - return ImmutableSortedSet.copyOfSorted(master.getPermissions(false)); + return ImmutableSortedSet.copyOfSorted(master.mergePermissionsToSortedSet()); } @Override public Set getEnduringPermissions() { - return ImmutableSet.copyOf(master.getNodes()); + return ImmutableSet.copyOf(master.getNodes().values()); } @Override public Set getTransientPermissions() { - return ImmutableSet.copyOf(master.getTransientNodes()); + return ImmutableSet.copyOf(master.getTransientNodes().values()); } @Override public SortedSet getAllNodes(@NonNull Contexts contexts) { - return new TreeSet<>(master.getAllNodes(null, ExtractedContexts.generate(contexts))); + return new TreeSet<>(master.resolveInheritancesAlmostEqual(ExtractedContexts.generate(contexts))); } @Override public Set getAllNodesFiltered(@NonNull Contexts contexts) { - return new HashSet<>(master.getAllNodesFiltered(ExtractedContexts.generate(contexts))); + return new HashSet<>(master.getAllNodes(ExtractedContexts.generate(contexts))); } @Override public Map exportNodes(Contexts contexts, boolean lowerCase) { - return new HashMap<>(master.exportNodes(contexts, lowerCase)); + return new HashMap<>(master.exportNodes(ExtractedContexts.generate(contexts), lowerCase)); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/MetaHolder.java b/common/src/main/java/me/lucko/luckperms/common/caching/MetaAccumulator.java similarity index 96% rename from common/src/main/java/me/lucko/luckperms/common/caching/MetaHolder.java rename to common/src/main/java/me/lucko/luckperms/common/caching/MetaAccumulator.java index c35c6544..70503cf3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/MetaHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/MetaAccumulator.java @@ -41,7 +41,7 @@ import java.util.TreeMap; */ @Getter @ToString -public class MetaHolder { +public class MetaAccumulator { @Getter(AccessLevel.NONE) private final Map meta; @@ -52,7 +52,7 @@ public class MetaHolder { private final MetaStack prefixStack; private final MetaStack suffixStack; - public MetaHolder(MetaStack prefixStack, MetaStack suffixStack) { + public MetaAccumulator(MetaStack prefixStack, MetaStack suffixStack) { this.meta = new HashMap<>(); this.prefixes = new TreeMap<>(Comparator.reverseOrder()); this.suffixes = new TreeMap<>(Comparator.reverseOrder()); @@ -60,7 +60,7 @@ public class MetaHolder { this.suffixStack = suffixStack; } - public MetaHolder() { + public MetaAccumulator() { this(NoopMetaStack.INSTANCE, NoopMetaStack.INSTANCE); } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java b/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java index 15ab240b..95ebad97 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java @@ -59,7 +59,7 @@ public class MetaCache implements MetaData { @Getter private MetaStack suffixStack = NoopMetaStack.INSTANCE; - public void loadMeta(MetaHolder meta) { + public void loadMeta(MetaAccumulator meta) { lock.writeLock().lock(); try { this.meta = ImmutableMap.copyOf(meta.getMeta()); diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java index 94443565..5d5e93ec 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java @@ -69,7 +69,7 @@ public class UserCache implements UserData { @Override public ListenableFuture reload(Contexts contexts, PermissionCache oldData) { - oldData.comparePermissions(user.exportNodes(contexts, true)); + oldData.comparePermissions(user.exportNodes(ExtractedContexts.generate(contexts), true)); return Futures.immediateFuture(oldData); } }); @@ -102,7 +102,7 @@ public class UserCache implements UserData { @Override public PermissionCache calculatePermissions(@NonNull Contexts contexts) { PermissionCache data = new PermissionCache(contexts, user, calculatorFactory); - data.setPermissions(user.exportNodes(contexts, true)); + data.setPermissions(user.exportNodes(ExtractedContexts.generate(contexts), true)); return data; } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java index d0f471b0..066529b2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/handlers/CachedStateManager.java @@ -27,24 +27,22 @@ import lombok.RequiredArgsConstructor; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -import me.lucko.luckperms.common.core.model.PermissionHolder; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import java.util.HashSet; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; /** * Manages the cached state of all permission holders */ @RequiredArgsConstructor public class CachedStateManager { - private static final Consumer INVALIDATE_CONSUMER = PermissionHolder::invalidateInheritanceCaches; + // private static final Consumer INVALIDATE_CONSUMER = PermissionHolder::invalidateInheritanceCaches; private final LuckPermsPlugin plugin; - // Group --> Groups that inherit from that group. (reverse relationship) + // Group --> Groups/Users that inherit from that group. (reverse relationship) private final Multimap map = HashMultimap.create(); private final ReentrantLock lock = new ReentrantLock(); @@ -117,6 +115,7 @@ public class CachedStateManager { } } + /* public void invalidateInheritances(HolderReference holder) { Set toInvalidate = getInheritances(holder); invalidateInheritances(plugin, toInvalidate); @@ -125,5 +124,6 @@ public class CachedStateManager { public static void invalidateInheritances(LuckPermsPlugin plugin, Set references) { references.forEach(hr -> hr.apply(plugin, INVALIDATE_CONSUMER)); } + */ } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/stacking/MetaStackElement.java b/common/src/main/java/me/lucko/luckperms/common/caching/stacking/MetaStackElement.java index 15e88f84..dc8b03b8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/stacking/MetaStackElement.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/stacking/MetaStackElement.java @@ -23,6 +23,7 @@ package me.lucko.luckperms.common.caching.stacking; import me.lucko.luckperms.api.LocalizedNode; +import me.lucko.luckperms.api.Node; import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; @@ -44,7 +45,7 @@ public interface MetaStackElement { * @param node the node to check * @return true if the accumulation should return */ - static boolean checkMetaType(boolean expectingPrefix, LocalizedNode node) { + static boolean checkMetaType(boolean expectingPrefix, Node node) { if (expectingPrefix) { if (!node.isPrefix()) { return true; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaInfo.java index e1ebf629..49a83164 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaInfo.java @@ -60,7 +60,7 @@ public class MetaInfo extends SharedSubCommand { Set meta = new HashSet<>(); // Collect data - for (LocalizedNode node : holder.getAllNodes(null, ExtractedContexts.generate(Contexts.allowAll()))) { + for (LocalizedNode node : holder.resolveInheritancesAlmostEqual(ExtractedContexts.generate(Contexts.allowAll()))) { if (!node.isSuffix() && !node.isPrefix() && !node.isMeta()) { continue; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemovePrefix.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemovePrefix.java index 76b273a8..e569aba1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemovePrefix.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemovePrefix.java @@ -66,7 +66,7 @@ public class MetaRemovePrefix extends SharedSubCommand { // Handle bulk removal if (prefix.equalsIgnoreCase("null")) { List toRemove = new ArrayList<>(); - for (Node node : holder.getNodes()) { + for (Node node : holder.getNodes().values()) { if (!node.isPrefix()) continue; if (node.getPrefix().getKey() != priority) continue; if (node.isTemporary()) continue; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveSuffix.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveSuffix.java index 0895c052..f60f030a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveSuffix.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveSuffix.java @@ -66,7 +66,7 @@ public class MetaRemoveSuffix extends SharedSubCommand { // Handle bulk removal if (suffix.equalsIgnoreCase("null")) { List toRemove = new ArrayList<>(); - for (Node node : holder.getNodes()) { + for (Node node : holder.getNodes().values()) { if (!node.isSuffix()) continue; if (node.getSuffix().getKey() != priority) continue; if (node.isTemporary()) continue; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempPrefix.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempPrefix.java index c6d28d2e..77ef1f71 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempPrefix.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempPrefix.java @@ -66,7 +66,7 @@ public class MetaRemoveTempPrefix extends SharedSubCommand { // Handle bulk removal if (prefix.equalsIgnoreCase("null")) { List toRemove = new ArrayList<>(); - for (Node node : holder.getNodes()) { + for (Node node : holder.getNodes().values()) { if (!node.isPrefix()) continue; if (node.getPrefix().getKey() != priority) continue; if (node.isPermanent()) continue; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempSuffix.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempSuffix.java index d3bdb557..4ab15135 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempSuffix.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/meta/MetaRemoveTempSuffix.java @@ -66,7 +66,7 @@ public class MetaRemoveTempSuffix extends SharedSubCommand { // Handle bulk removal if (suffix.equalsIgnoreCase("null")) { List toRemove = new ArrayList<>(); - for (Node node : holder.getNodes()) { + for (Node node : holder.getNodes().values()) { if (!node.isSuffix()) continue; if (node.getSuffix().getKey() != priority) continue; if (node.isPermanent()) continue; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderShowTracks.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderShowTracks.java index 3adcf9b2..68f920f9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderShowTracks.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/other/HolderShowTracks.java @@ -51,7 +51,7 @@ public class HolderShowTracks extends SubCommand return CommandResult.LOADING_ERROR; } - Set nodes = holder.getNodes().stream() + Set nodes = holder.getNodes().values().stream() .filter(Node::isGroupNode) .filter(Node::isPermanent) .collect(Collectors.toSet()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java index 884f02ca..324e6382 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/parent/ParentInfo.java @@ -43,8 +43,8 @@ public class ParentInfo extends SharedSubCommand { @Override public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHolder holder, List args, String label) throws CommandException { - Message.LISTPARENTS.send(sender, holder.getFriendlyName(), Util.permGroupsToString(holder.getPermissions(false))); - Message.LISTPARENTS_TEMP.send(sender, holder.getFriendlyName(), Util.tempGroupsToString(holder.getPermissions(false))); + Message.LISTPARENTS.send(sender, holder.getFriendlyName(), Util.permGroupsToString(holder.mergePermissionsToSortedSet())); + Message.LISTPARENTS_TEMP.send(sender, holder.getFriendlyName(), Util.tempGroupsToString(holder.mergePermissionsToSortedSet())); return CommandResult.SUCCESS; } } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java index e92393ef..afc10db1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/generic/permission/PermissionInfo.java @@ -55,11 +55,11 @@ public class PermissionInfo extends SharedSubCommand { public CommandResult execute(LuckPermsPlugin plugin, Sender sender, PermissionHolder holder, List args, String label) throws CommandException { if (sender.getUuid().equals(Constants.CONSOLE_UUID)) { Message.LISTNODES.send(sender, holder.getFriendlyName()); - sender.sendMessage(Util.color(Util.permNodesToStringConsole(holder.getPermissions(false)))); + sender.sendMessage(Util.color(Util.permNodesToStringConsole(holder.mergePermissionsToSortedSet()))); } else { int page = ArgumentUtils.handleIntOrElse(0, args, 1); - Map.Entry ent = Util.permNodesToMessage(holder.getPermissions(false), holder, label, page); + Map.Entry ent = Util.permNodesToMessage(holder.mergePermissionsToSortedSet(), holder, label, page); if (ent.getValue() != null) { Message.LISTNODES_WITH_PAGE.send(sender, holder.getFriendlyName(), ent.getValue()); sender.sendMessage(ent.getKey()); @@ -69,7 +69,7 @@ public class PermissionInfo extends SharedSubCommand { } } - Message.LISTNODES_TEMP.send(sender, holder.getFriendlyName(), Util.tempNodesToString(holder.getPermissions(false))); + Message.LISTNODES_TEMP.send(sender, holder.getFriendlyName(), Util.tempNodesToString(holder.mergePermissionsToSortedSet())); return CommandResult.SUCCESS; } } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupBulkChange.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupBulkChange.java index 5d038128..c405112a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupBulkChange.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupBulkChange.java @@ -70,7 +70,7 @@ public class GroupBulkChange extends SubCommand { return CommandResult.FAILURE; } - Iterator iterator = group.getNodes().iterator(); + Iterator iterator = group.getNodes().values().iterator(); if (type.equals("world")) { while (iterator.hasNext()) { Node element = iterator.next(); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupClone.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupClone.java index 66cbe87e..1750305c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupClone.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupClone.java @@ -61,7 +61,7 @@ public class GroupClone extends SubCommand { return CommandResult.LOADING_ERROR; } - newGroup.setNodes(group.getNodes()); + newGroup.replaceNodes(group.getNodes()); Message.CLONE_SUCCESS.send(sender, group.getName(), newGroup.getName()); LogEntry.build().actor(sender).acted(group).action("clone " + newGroup.getName()).build().submit(plugin, sender); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupInfo.java index fe845de1..621511e4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupInfo.java @@ -57,12 +57,12 @@ public class GroupInfo extends SubCommand { group.getMetaNodes().size() ); - Set parents = group.getPermissions(false).stream() + Set parents = group.mergePermissions().stream() .filter(Node::isGroupNode) .filter(Node::isPermanent) .collect(Collectors.toSet()); - Set tempParents = group.getPermissions(false).stream() + Set tempParents = group.mergePermissions().stream() .filter(Node::isGroupNode) .filter(Node::isTemporary) .collect(Collectors.toSet()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupRename.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupRename.java index 580917d7..8dd9098b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupRename.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupRename.java @@ -75,7 +75,7 @@ public class GroupRename extends SubCommand { return CommandResult.FAILURE; } - newGroup.setNodes(group.getNodes()); + newGroup.replaceNodes(group.getNodes()); Message.RENAME_SUCCESS.send(sender, group.getName(), newGroup.getName()); LogEntry.build().actor(sender).acted(group).action("rename " + newGroup.getName()).build().submit(plugin, sender); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupSetWeight.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupSetWeight.java index 21152dd4..8169ed3a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupSetWeight.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/group/GroupSetWeight.java @@ -22,7 +22,6 @@ package me.lucko.luckperms.common.commands.impl.group; -import me.lucko.luckperms.api.Node; import me.lucko.luckperms.common.commands.Arg; import me.lucko.luckperms.common.commands.CommandException; import me.lucko.luckperms.common.commands.CommandResult; @@ -35,12 +34,8 @@ import me.lucko.luckperms.common.core.NodeFactory; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.utils.Predicates; -import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; -import me.lucko.luckperms.exceptions.ObjectLacksException; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; public class GroupSetWeight extends SubCommand { public GroupSetWeight() { @@ -53,19 +48,8 @@ public class GroupSetWeight extends SubCommand { public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group group, List args, String label) throws CommandException { int weight = ArgumentUtils.handlePriority(0, args); - Set existingWeightNodes = group.getNodes().stream() - .filter(n -> n.getPermission().startsWith("weight.")) - .collect(Collectors.toSet()); - - existingWeightNodes.forEach(n -> { - try { - group.unsetPermission(n); - } catch (ObjectLacksException ignored) {} - }); - - try { - group.setPermission(NodeFactory.newBuilder("weight." + weight).build()); - } catch (ObjectAlreadyHasException ignored) {} + group.removeIf(n -> n.getPermission().startsWith("weight.")); + group.setPermissionUnchecked(NodeFactory.newBuilder("weight." + weight).build()); save(group, sender, plugin); Message.GROUP_SET_WEIGHT.send(sender, weight, group.getDisplayName()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserBulkChange.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserBulkChange.java index f0e9251c..87996ae3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserBulkChange.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserBulkChange.java @@ -70,7 +70,7 @@ public class UserBulkChange extends SubCommand { return CommandResult.FAILURE; } - Iterator iterator = user.getNodes().iterator(); + Iterator iterator = user.getNodes().values().iterator(); if (type.equals("world")) { while (iterator.hasNext()) { Node element = iterator.next(); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserDemote.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserDemote.java index 16c956c8..bb94f080 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserDemote.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserDemote.java @@ -91,7 +91,7 @@ public class UserDemote extends SubCommand { // Load applicable groups Set nodes = new HashSet<>(); - for (Node node : user.getNodes()) { + for (Node node : user.getNodes().values()) { if (!node.isGroupNode()) { continue; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserInfo.java index bfd14104..a2324446 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserInfo.java @@ -62,12 +62,12 @@ public class UserInfo extends SubCommand { user.getMetaNodes().size() ); - Set parents = user.getPermissions(false).stream() + Set parents = user.mergePermissions().stream() .filter(Node::isGroupNode) .filter(Node::isPermanent) .collect(Collectors.toSet()); - Set tempParents = user.getPermissions(false).stream() + Set tempParents = user.mergePermissions().stream() .filter(Node::isGroupNode) .filter(Node::isTemporary) .collect(Collectors.toSet()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserPromote.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserPromote.java index a5c60f64..5bb43065 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserPromote.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/user/UserPromote.java @@ -91,7 +91,7 @@ public class UserPromote extends SubCommand { // Load applicable groups Set nodes = new HashSet<>(); - for (Node node : user.getNodes()) { + for (Node node : user.getNodes().values()) { if (!node.isGroupNode()) { continue; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditGroup.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditGroup.java index 5e05061e..ea819c04 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditGroup.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditGroup.java @@ -82,7 +82,7 @@ public class BulkEditGroup extends SubCommand { Set toAdd = new HashSet<>(); Set toRemove = new HashSet<>(); - Iterator iterator = user.getNodes().iterator(); + Iterator iterator = user.getNodes().values().iterator(); if (type.equals("world")) { while (iterator.hasNext()) { Node element = iterator.next(); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditPermission.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditPermission.java index e77b09b7..d36b3b7c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditPermission.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/usersbulkedit/BulkEditPermission.java @@ -82,7 +82,7 @@ public class BulkEditPermission extends SubCommand { Set toAdd = new HashSet<>(); Set toRemove = new HashSet<>(); - Iterator iterator = user.getNodes().iterator(); + Iterator iterator = user.getNodes().values().iterator(); if (type.equals("world")) { while (iterator.hasNext()) { Node element = iterator.next(); diff --git a/common/src/main/java/me/lucko/luckperms/common/core/ContextSetComparator.java b/common/src/main/java/me/lucko/luckperms/common/core/ContextSetComparator.java new file mode 100644 index 00000000..7484b454 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/core/ContextSetComparator.java @@ -0,0 +1,115 @@ +/* + * 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.common.core; + +import me.lucko.luckperms.api.context.ImmutableContextSet; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeSet; + +public class ContextSetComparator implements Comparator { + private static final Comparator> STRING_ENTRY_COMPARATOR = (o1, o2) -> { + int ret = o1.getKey().compareTo(o2.getKey()); + if (ret != 0) { + return ret; + } + + return o1.getValue().compareTo(o2.getValue()); + }; + + private static final ContextSetComparator INSTANCE = new ContextSetComparator(); + public static Comparator get() { + return INSTANCE; + } + + public static Comparator reverse() { + return INSTANCE.reversed(); + } + + @Override + public int compare(ImmutableContextSet o1, ImmutableContextSet o2) { + if (o1.equals(o2)) { + return 0; + } + + boolean o1ServerSpecific = o1.containsKey("server"); + boolean o2ServerSpecific = o2.containsKey("server"); + if (o1ServerSpecific != o2ServerSpecific) { + return o1ServerSpecific ? 1 : -1; + } + + boolean o1WorldSpecific = o1.containsKey("world"); + boolean o2WorldSpecific = o2.containsKey("world"); + if (o1WorldSpecific != o2WorldSpecific) { + return o1WorldSpecific ? 1 : -1; + } + + int o1Size = o1.size(); + int o2Size = o2.size(); + if (o1Size != o2Size) { + return o1Size > o2Size ? 1 : -1; + } + + // we *have* to maintain transitivity in this comparator. this may be expensive, but it's necessary, as these + // values are stored in a treemap. + + // in order to have consistent ordering, we have to compare the content of the context sets by ordering the + // elements and then comparing which set is greater. + TreeSet> o1Map = new TreeSet<>(STRING_ENTRY_COMPARATOR); + TreeSet> o2Map = new TreeSet<>(STRING_ENTRY_COMPARATOR); + + o1Map.addAll(o1.toMultimap().entries()); + o2Map.addAll(o2.toMultimap().entries()); + + int o1MapSize = o1Map.size(); + int o2MapSize = o2Map.size(); + if (o1MapSize != o2MapSize) { + return o1MapSize > o2MapSize ? 1 : -1; + } + + // size is definitely the same + Iterator> it1 = o1Map.iterator(); + Iterator> it2 = o2Map.iterator(); + + while (it1.hasNext()) { + Map.Entry ent1 = it1.next(); + Map.Entry ent2 = it2.next(); + + // compare these values. + if (ent1.getKey().equals(ent2.getKey()) && ent1.getValue().equals(ent2.getValue())) { + // identical entries. just move on + continue; + } + + // these values are at the same position in the ordered sets. + // if ent1 is "greater" than ent2, then at this first position, o1 has a "greater" entry, and can therefore be considered + // a greater set. + return STRING_ENTRY_COMPARATOR.compare(ent1, ent2); + } + + // shouldn't ever reach this point. ever. + return 0; + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/core/NodeComparator.java b/common/src/main/java/me/lucko/luckperms/common/core/NodeComparator.java new file mode 100644 index 00000000..a6506e4e --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/core/NodeComparator.java @@ -0,0 +1,64 @@ +/* + * 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.common.core; + +import me.lucko.luckperms.api.Node; + +import java.util.Comparator; + +public class NodeComparator implements Comparator { + private static final NodeComparator INSTANCE = new NodeComparator(); + public static Comparator get() { + return INSTANCE; + } + + public static Comparator reverse() { + return INSTANCE.reversed(); + } + + @Override + public int compare(Node o1, Node o2) { + if (o1.equals(o2)) { + return 0; + } + + 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 PriorityComparator.get().compareStrings(o1.getPermission(), o2.getPermission()) == 1 ? -1 : 1; + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/core/PriorityComparator.java b/common/src/main/java/me/lucko/luckperms/common/core/PriorityComparator.java index afc89e26..1f3735a9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/PriorityComparator.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/PriorityComparator.java @@ -102,13 +102,19 @@ public class PriorityComparator implements Comparator { public int compareStrings(String o1, String o2) { if (o1.equals(o2)) { - return 1; + return 0; } try { CollationKey o1c = collationKeyCache.get(o1); CollationKey o2c = collationKeyCache.get(o2); - return o1c.compareTo(o2c) == 1 ? 1 : -1; + int i = o1c.compareTo(o2c); + if (i != 0) { + return i; + } + + // fallback to standard string comparison + return o1.compareTo(o2); } catch (Exception e) { // ignored } diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java index 549af7f8..70b3ace0 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/PermissionHolder.java @@ -26,43 +26,44 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.Maps; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.SortedSetMultimap; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.common.api.delegates.PermissionHolderDelegate; -import me.lucko.luckperms.common.caching.MetaHolder; -import me.lucko.luckperms.common.caching.handlers.CachedStateManager; +import me.lucko.luckperms.common.caching.MetaAccumulator; import me.lucko.luckperms.common.caching.handlers.GroupReference; import me.lucko.luckperms.common.caching.handlers.HolderReference; -import me.lucko.luckperms.common.caching.holder.ExportNodesHolder; -import me.lucko.luckperms.common.caching.holder.GetAllNodesRequest; -import me.lucko.luckperms.common.commands.utils.Util; import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.core.ContextSetComparator; import me.lucko.luckperms.common.core.InheritanceInfo; +import me.lucko.luckperms.common.core.NodeComparator; import me.lucko.luckperms.common.core.NodeFactory; import me.lucko.luckperms.common.core.PriorityComparator; import me.lucko.luckperms.common.core.TemporaryModifier; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.utils.Cache; import me.lucko.luckperms.common.utils.ExtractedContexts; import me.lucko.luckperms.common.utils.ImmutableLocalizedNode; -import me.lucko.luckperms.common.utils.WeightComparator; +import me.lucko.luckperms.common.utils.NodeTools; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -71,22 +72,38 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; import java.util.stream.Collectors; /** - * Represents an object that can hold permissions - * For example a User or a Group + * Represents an object that can hold permissions, (a user or group) + * + *

Permissions are stored in Multimaps, with the context of the node being the key, and the actual Node object being + * the value. The keys (context sets) are ordered according to their weight {@link ContextSetComparator}, and the values + * are ordered according to the priority of the node, according to {@link NodeComparator}.

+ * + *

This class also provides a number of methods to perform inheritance lookups. These lookup methods initially use + * Lists of nodes populated with the inheritance tree. Nodes at the start of this list have priority over nodes at the + * end. Nodes higher up the tree appear at the end of these lists. In order to remove duplicate elements, the lists are + * flattened using the methods in {@link NodeTools}. This is significantly faster than trying to prevent duplicates + * throughout the process of accumulation, and reduces the need for too much caching.

+ * + *

Cached state is avoided in these instances to cut down on memory footprint. The nodes are stored indexed to the + * contexts they apply in, so doing context specific querying should be fast. Caching would be ineffective here, due to + * the potentially vast amount of contexts being used by nodes, and the potential for very large inheritance trees.

*/ @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public abstract class PermissionHolder { /** - * The UUID of the user / name of the group. - * Used to prevent circular inheritance issues + * The name of this PermissionHolder object. + * + *

Used to prevent circular inheritance issues.

+ * + *

For users, this value is a String representation of their {@link User#getUuid()}. For groups, it's just the + * {@link Group#getName()}.

*/ @Getter private final String objectName; @@ -98,14 +115,38 @@ public abstract class PermissionHolder { private final LuckPermsPlugin plugin; /** - * The holders persistent nodes + * The holders persistent nodes. + * + *

These are nodes which are never stored or persisted to a file, and only + * last until the end of the objects lifetime. (for a group, that's when the server stops, and for a user, it's when + * they log out, or get unloaded.)

+ * + *

Nodes are mapped by the result of {@link Node#getFullContexts()}, and keys are sorted by the weight of the + * ContextSet. ContextSets are ordered first by the presence of a server key, then by the presence of a world + * key, and finally by the overall size of the set. Nodes are ordered according to the priority rules + * defined in {@link NodeComparator}.

*/ - private final Set nodes = new HashSet<>(); + private final SortedSetMultimap nodes = MultimapBuilder + .treeKeys(ContextSetComparator.reverse()) + .treeSetValues(NodeComparator.reverse()) + .build(); /** - * The holders transient nodes + * The holders transient nodes. + * + *

These are nodes which are never stored or persisted to a file, and only + * last until the end of the objects lifetime. (for a group, that's when the server stops, and for a user, it's when + * they log out, or get unloaded.)

+ * + *

Nodes are mapped by the result of {@link Node#getFullContexts()}, and keys are sorted by the weight of the + * ContextSet. ContextSets are ordered first by the presence of a server key, then by the presence of a world + * key, and finally by the overall size of the set. Nodes are ordered according to the priority rules + * defined in {@link NodeComparator}.

*/ - private final Set transientNodes = new HashSet<>(); + private final SortedSetMultimap transientNodes = MultimapBuilder + .treeKeys(ContextSetComparator.reverse()) + .treeSetValues(NodeComparator.reverse()) + .build(); /** * Lock used by Storage implementations to prevent concurrent read/writes @@ -113,80 +154,13 @@ public abstract class PermissionHolder { @Getter private final Lock ioLock = new ReentrantLock(); + /** + * A set of runnables which are called when this objects state changes. + */ @Getter private final Set stateListeners = ConcurrentHashMap.newKeySet(); - - /* - * CACHES - cache the result of a number of methods in this class, until they are invalidated. - */ - - /* Internal Caches - only depend on the state of this instance. */ - - // Just holds immutable copies of the node sets. - private Cache> enduringCache = new Cache<>(() -> { - synchronized (nodes) { - return ImmutableSet.copyOf(nodes); - } - }); - private Cache> transientCache = new Cache<>(() -> { - synchronized (transientNodes) { - return ImmutableSet.copyOf(transientNodes); - } - }); - - // Merges transient and persistent nodes together, and converts Node instances to a localized form. - private Cache> cache = new Cache<>(() -> cacheApply(false)); - // Same as the cache above, except this merges temporary values with any permanent values if any are matching. - private Cache> mergedCache = new Cache<>(() -> cacheApply(true)); - - /* External Caches - may depend on the state of other instances. */ - - private LoadingCache> getAllNodesCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .build(new CacheLoader>() { - @Override - public SortedSet load(GetAllNodesRequest getAllNodesHolder) { - return getAllNodesCacheApply(getAllNodesHolder); - } - }); - private LoadingCache> getAllNodesFilteredCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .build(new CacheLoader>() { - @Override - public Set load(ExtractedContexts extractedContexts) throws Exception { - return getAllNodesFilteredApply(extractedContexts); - } - }); - private LoadingCache> exportNodesCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .build(new CacheLoader>() { - @Override - public Map load(ExportNodesHolder exportNodesHolder) throws Exception { - return exportNodesApply(exportNodesHolder); - } - }); - - protected void forceCleanup() { - getAllNodesCache.cleanUp(); - getAllNodesFilteredCache.cleanUp(); - exportNodesCache.cleanUp(); - } - - private void invalidateCache(boolean enduring) { - if (enduring) { - enduringCache.invalidate(); - } else { - transientCache.invalidate(); - } - cache.invalidate(); - mergedCache.invalidate(); - - // Invalidate inheritance caches - getAllNodesCache.invalidateAll(); - getAllNodesFilteredCache.invalidateAll(); - exportNodesCache.invalidateAll(); - + private void invalidateCache() { // Invalidate listeners for (Runnable r : stateListeners) { try { @@ -196,171 +170,392 @@ public abstract class PermissionHolder { } } - // Get previous references - Set refs = plugin.getCachedStateManager().getInheritances(toReference()); - // Declare new state to the state manager declareState(); - - // Add all new references affected by the state change. - refs.addAll(plugin.getCachedStateManager().getInheritances(toReference())); - - // Invalidate all affected children. - CachedStateManager.invalidateInheritances(plugin, refs); } - public void invalidateInheritanceCaches() { - getAllNodesCache.invalidateAll(); - getAllNodesFilteredCache.invalidateAll(); - exportNodesCache.invalidateAll(); - declareState(); + protected void declareState() { + /* only declare state of groups. the state manager isn't really being used now the caches in this class + are gone, but it's useful for command output. */ + if (this instanceof Group) { + plugin.getCachedStateManager().putAll(toReference(), getGroupReferences()); + } } - /* Caching apply methods. Are just called by the caching instances to gather data about the instance. */ + /** + * Gets the friendly name of this permission holder (for use in commands, etc) + * + * @return the holders "friendly" name + */ + public abstract String getFriendlyName(); - private ImmutableSortedSet cacheApply(boolean mergeTemp) { - TreeSet combined = new TreeSet<>(PriorityComparator.reverse()); - Set enduring = getNodes(); - if (!enduring.isEmpty()) { - combined.addAll(enduring.stream() - .map(n -> ImmutableLocalizedNode.of(n, getObjectName())) - .collect(Collectors.toList()) - ); + /** + * Forms a HolderReference for this PermissionHolder. + * + * @return this holders reference + */ + public abstract HolderReference toReference(); + + /** + * Gets the API delegate for this instance + * + * @return the api delegate + */ + public abstract PermissionHolderDelegate getDelegate(); + + /** + * Returns an immutable copy of this objects nodes + * + * @return an immutable copy of the multimap storing this objects nodes + */ + public ImmutableSetMultimap getNodes() { + synchronized (nodes) { + return ImmutableSetMultimap.copyOf(nodes); } - Set tran = getTransientNodes(); - if (!tran.isEmpty()) { - combined.addAll(tran.stream() - .map(n -> ImmutableLocalizedNode.of(n, getObjectName())) - .collect(Collectors.toList()) - ); + } + + /** + * Returns an immutable copy of this objects transient nodes + * + * @return an immutable copy of the multimap storing this objects transient nodes + */ + public ImmutableSetMultimap getTransientNodes() { + synchronized (transientNodes) { + return ImmutableSetMultimap.copyOf(transientNodes); + } + } + + /** + * Sets this objects nodes to the values in the set + * + * @param set the set of nodes to apply to the object + */ + public void setNodes(Set set) { + synchronized (nodes) { + nodes.clear(); + for (Node n : set) { + nodes.put(n.getFullContexts().makeImmutable(), n); + } + } + invalidateCache(); + } + + /** + * Replaces the multimap backing this object with another + * + * @param multimap the replacement multimap + */ + public void replaceNodes(Multimap multimap) { + synchronized (nodes) { + nodes.clear(); + nodes.putAll(multimap); + } + invalidateCache(); + } + + public void setTransientNodes(Set set) { + synchronized (transientNodes) { + transientNodes.clear(); + for (Node n : set) { + transientNodes.put(n.getFullContexts().makeImmutable(), n); + } + } + invalidateCache(); + } + + public void replaceTransientNodes(Multimap multimap) { + synchronized (transientNodes) { + transientNodes.clear(); + transientNodes.putAll(multimap); + } + invalidateCache(); + } + + /** + * Merges enduring and transient permissions into one set + * + * @return a set containing the holders enduring and transient permissions + */ + public LinkedHashSet mergePermissions() { + LinkedHashSet ret = new LinkedHashSet<>(); + synchronized (transientNodes) { + ret.addAll(transientNodes.values()); } - Iterator it = combined.iterator(); - Set higherPriority = new HashSet<>(); + synchronized (nodes) { + ret.addAll(nodes.values()); + } - iterate: - while (it.hasNext()) { - LocalizedNode entry = it.next(); - for (LocalizedNode h : higherPriority) { - if (mergeTemp ? entry.getNode().equalsIgnoringValueOrTemp(h.getNode()) : entry.getNode().almostEquals(h.getNode())) { - it.remove(); - continue iterate; + return ret; + } + + public List mergePermissionsToList() { + List ret = new ArrayList<>(); + synchronized (transientNodes) { + ret.addAll(transientNodes.values()); + } + synchronized (nodes) { + ret.addAll(nodes.values()); + } + + return ret; + } + + public SortedSet mergePermissionsToSortedSet() { + SortedSet ret = new TreeSet<>(PriorityComparator.reverse()); + ret.addAll(mergePermissions().stream().map(n -> ImmutableLocalizedNode.of(n, getObjectName())).collect(Collectors.toSet())); + return ret; + } + + public LinkedHashSet flattenNodes() { + synchronized (nodes) { + return new LinkedHashSet<>(nodes.values()); + } + } + + public LinkedHashSet flattenNodes(ContextSet filter) { + synchronized (nodes) { + LinkedHashSet set = new LinkedHashSet<>(); + for (Map.Entry> e : nodes.asMap().entrySet()) { + if (e.getKey().isSatisfiedBy(filter)) { + set.addAll(e.getValue()); } } - higherPriority.add(entry); + + return set; } - return ImmutableSortedSet.copyOfSorted(combined); } - private SortedSet getAllNodesCacheApply(GetAllNodesRequest getAllNodesHolder) { - // Expand the holder. - Set excludedGroups = new HashSet<>(getAllNodesHolder.getExcludedGroups()); - ExtractedContexts contexts = getAllNodesHolder.getContexts(); + public LinkedHashSet flattenTransientNodes() { + synchronized (transientNodes) { + return new LinkedHashSet<>(transientNodes.values()); + } + } - // Don't register users, as they cannot be inherited. - if (!(this instanceof User)) { + public LinkedHashSet flattenTransientNodes(ContextSet filter) { + synchronized (transientNodes) { + LinkedHashSet set = new LinkedHashSet<>(); + for (Map.Entry> e : transientNodes.asMap().entrySet()) { + if (e.getKey().isSatisfiedBy(filter)) { + set.addAll(e.getValue()); + } + } + + return set; + } + } + + public List flattenNodesToList() { + synchronized (nodes) { + return new ArrayList<>(nodes.values()); + } + } + + public List flattenNodesToList(ContextSet filter) { + synchronized (nodes) { + List set = new ArrayList<>(); + for (Map.Entry> e : nodes.asMap().entrySet()) { + if (e.getKey().isSatisfiedBy(filter)) { + set.addAll(e.getValue()); + } + } + + return set; + } + } + + public List flattenTransientNodesToList() { + synchronized (transientNodes) { + return new ArrayList<>(transientNodes.values()); + } + } + + public List flattenTransientNodesToList(ContextSet filter) { + synchronized (transientNodes) { + if (transientNodes.isEmpty()) { + return Collections.emptyList(); + } + + List set = new ArrayList<>(); + for (Map.Entry> e : transientNodes.asMap().entrySet()) { + if (e.getKey().isSatisfiedBy(filter)) { + set.addAll(e.getValue()); + } + } + + return set; + } + } + + public boolean removeIf(Predicate predicate) { + boolean result; + ImmutableSet before = ImmutableSet.copyOf(flattenNodes()); + + synchronized (nodes) { + result = nodes.values().removeIf(predicate); + } + + if (!result) { + return false; + } + + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(flattenNodes()); + plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); + return true; + } + + public boolean removeIfTransient(Predicate predicate) { + boolean result; + + synchronized (nodes) { + result = transientNodes.values().removeIf(predicate); + } + + if (result) { + invalidateCache(); + } + + return result; + } + + /** + * Resolves inherited nodes and returns them + * + * @param excludedGroups a list of groups to exclude + * @param context context to decide if groups should be applied + * @return a set of nodes + */ + protected List resolveInheritances(List accumulator, Set excludedGroups, ExtractedContexts context) { + if (accumulator == null) { + accumulator = new ArrayList<>(); + } + + if (excludedGroups == null) { + excludedGroups = new HashSet<>(); + } + + if (this instanceof Group) { excludedGroups.add(getObjectName().toLowerCase()); } - // Get the objects base permissions. - SortedSet all = new TreeSet<>((SortedSet) getPermissions(true)); + // get the objects own nodes + flattenTransientNodesToList(context.getContextSet()).stream() + .map(n -> ImmutableLocalizedNode.of(n, getObjectName())) + .forEach(accumulator::add); - Contexts context = contexts.getContexts(); - String server = contexts.getServer(); - String world = contexts.getWorld(); + flattenNodesToList(context.getContextSet()).stream() + .map(n -> ImmutableLocalizedNode.of(n, getObjectName())) + .forEach(accumulator::add); - Set parents = all.stream() - .map(LocalizedNode::getNode) + Contexts contexts = context.getContexts(); + String server = context.getServer(); + String world = context.getWorld(); + + // screw effectively final + Set finalExcludedGroups = excludedGroups; + List finalAccumulator = accumulator; + mergePermissions().stream() .filter(Node::getValue) .filter(Node::isGroupNode) - .filter(n -> n.shouldApplyOnServer(server, context.isApplyGlobalGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX))) - .filter(n -> n.shouldApplyOnWorld(world, context.isApplyGlobalWorldGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX))) - .filter(n -> n.shouldApplyWithContext(contexts.getContextSet(), false)) - .collect(Collectors.toSet()); + .filter(n -> n.shouldApplyOnServer(server, contexts.isApplyGlobalGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX))) + .filter(n -> n.shouldApplyOnWorld(world, contexts.isApplyGlobalWorldGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX))) + .filter(n -> n.shouldApplyWithContext(contexts.getContexts(), false)) + .map(Node::getGroupName) + .distinct() + .map(n -> Optional.ofNullable(plugin.getGroupManager().getIfLoaded(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(g -> !finalExcludedGroups.contains(g.getObjectName().toLowerCase())) + .sorted((o1, o2) -> { + int result = Integer.compare(o1.getWeight().orElse(0), o2.getWeight().orElse(0)); + return result == 1 ? -1 : 1; + }) + .forEach(group -> group.resolveInheritances(finalAccumulator, finalExcludedGroups, context)); - // Resolve & sort parents into order before we apply. - TreeSet> sortedParents = new TreeSet<>(WeightComparator.INSTANCE.reversed()); - for (Node node : parents) { - Group group = plugin.getGroupManager().getIfLoaded(node.getGroupName()); - if (group != null) { - sortedParents.add(Maps.immutableEntry(group.getWeight().orElse(0), group)); - } - } - - for (Map.Entry e : sortedParents) { - if (excludedGroups.contains(e.getValue().getObjectName().toLowerCase())) { - continue; - } - - inherited: - for (LocalizedNode inherited : e.getValue().getAllNodes(excludedGroups, contexts)) { - for (LocalizedNode existing : all) { - if (existing.getNode().almostEquals(inherited.getNode())) { - continue inherited; - } - } - - all.add(inherited); - } - } - - return all; + return accumulator; } - private Set getAllNodesFilteredApply(ExtractedContexts contexts) { - Contexts context = contexts.getContexts(); - String server = contexts.getServer(); - String world = contexts.getWorld(); + public List resolveInheritances(ExtractedContexts context) { + return resolveInheritances(null, null, context); + } - SortedSet allNodes; - if (context.isApplyGroups()) { - allNodes = getAllNodes(null, contexts); + public SortedSet resolveInheritancesAlmostEqual(ExtractedContexts contexts) { + List nodes = resolveInheritances(null, null, contexts); + NodeTools.removeAlmostEqual(nodes.iterator()); + SortedSet ret = new TreeSet<>(PriorityComparator.reverse()); + ret.addAll(nodes); + return ret; + } + + public SortedSet resolveInheritancesMergeTemp(ExtractedContexts contexts) { + List nodes = resolveInheritances(null, null, contexts); + NodeTools.removeIgnoreValueOrTemp(nodes.iterator()); + SortedSet ret = new TreeSet<>(PriorityComparator.reverse()); + ret.addAll(nodes); + return ret; + } + + public SortedSet getAllNodes(ExtractedContexts context) { + Contexts contexts = context.getContexts(); + String server = context.getServer(); + String world = context.getWorld(); + + List entries; + if (contexts.isApplyGroups()) { + entries = resolveInheritances(null, null, context); } else { - allNodes = new TreeSet<>((SortedSet) getPermissions(true)); + entries = flattenNodesToList(context.getContextSet()).stream().map(n -> ImmutableLocalizedNode.of(n, getObjectName())).collect(Collectors.toList()); } - allNodes.removeIf(node -> + entries.removeIf(node -> !node.isGroupNode() && ( - !node.shouldApplyOnServer(server, context.isIncludeGlobal(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || - !node.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || - !node.shouldApplyWithContext(contexts.getContextSet(), false) + !node.shouldApplyOnServer(server, contexts.isIncludeGlobal(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || + !node.shouldApplyOnWorld(world, contexts.isIncludeGlobalWorld(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || + !node.shouldApplyWithContext(context.getContextSet(), false) ) ); - Set perms = ConcurrentHashMap.newKeySet(); - - all: - for (LocalizedNode ln : allNodes) { - // Force higher priority nodes to override - for (LocalizedNode alreadyIn : perms) { - if (ln.getNode().getPermission().equals(alreadyIn.getNode().getPermission())) { - continue all; - } - } - - perms.add(ln); - } - - return perms; + NodeTools.removeSamePermission(entries.iterator()); + SortedSet ret = new TreeSet<>(PriorityComparator.reverse()); + ret.addAll(entries); + return ret; } - private Map exportNodesApply(ExportNodesHolder exportNodesHolder) { - Contexts context = exportNodesHolder.getContexts(); - Boolean lowerCase = exportNodesHolder.getLowerCase(); + public Map exportNodes(ExtractedContexts context, boolean lowerCase) { + Contexts contexts = context.getContexts(); + String server = context.getServer(); + String world = context.getWorld(); + + List entries; + if (contexts.isApplyGroups()) { + entries = resolveInheritances(null, null, context); + } else { + entries = flattenNodesToList(context.getContextSet()); + } + + entries.removeIf(node -> + !node.isGroupNode() && ( + !node.shouldApplyOnServer(server, contexts.isIncludeGlobal(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || + !node.shouldApplyOnWorld(world, contexts.isIncludeGlobalWorld(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || + !node.shouldApplyWithContext(context.getContextSet(), false) + ) + ); Map perms = new HashMap<>(); - for (LocalizedNode ln : getAllNodesFiltered(ExtractedContexts.generate(context))) { - Node node = ln.getNode(); + for (Node node : entries) { + String perm = lowerCase ? node.getPermission().toLowerCase() : node.getPermission(); + if (!perms.containsKey(perm)) { + perms.put(perm, node.getValue()); - perms.put(lowerCase ? node.getPermission().toLowerCase() : node.getPermission(), node.getValue()); - - if (plugin.getConfiguration().get(ConfigKeys.APPLYING_SHORTHAND)) { - List sh = node.resolveShorthand(); - if (!sh.isEmpty()) { - sh.stream().map(s -> lowerCase ? s.toLowerCase() : s) - .filter(s -> !perms.containsKey(s)) - .forEach(s -> perms.put(s, node.getValue())); + if (plugin.getConfiguration().get(ConfigKeys.APPLYING_SHORTHAND)) { + List sh = node.resolveShorthand(); + if (!sh.isEmpty()) { + sh.stream().map(s -> lowerCase ? s.toLowerCase() : s) + .filter(s -> !perms.containsKey(s)) + .forEach(s -> perms.put(s, node.getValue())); + } } } } @@ -368,130 +563,9 @@ public abstract class PermissionHolder { return ImmutableMap.copyOf(perms); } - protected void declareState() { - plugin.getCachedStateManager().putAll(toReference(), getGroupReferences()); - } - - public abstract String getFriendlyName(); - public abstract HolderReference toReference(); - public abstract PermissionHolderDelegate getDelegate(); - - public Set getNodes() { - return enduringCache.get(); - } - - public void setNodes(Map nodes) { - Set set = nodes.entrySet().stream() - .map(e -> NodeFactory.fromSerialisedNode(e.getKey(), e.getValue())) - .collect(Collectors.toSet()); - - setNodes(set); - } - - public void setNodes(Set set) { - synchronized (nodes) { - if (nodes.equals(set)) { - return; - } - - nodes.clear(); - nodes.addAll(set); - } - invalidateCache(true); - } - - public boolean removeIf(Predicate predicate) { - boolean result; - ImmutableSet before = ImmutableSet.copyOf(getNodes()); - - synchronized (nodes) { - result = nodes.removeIf(predicate); - } - - if (!result) { - return false; - } - - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); - plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); - return true; - } - - public Set getTransientNodes() { - return transientCache.get(); - } - - public void setTransientNodes(Set set) { - synchronized (transientNodes) { - if (transientNodes.equals(set)) { - return; - } - - transientNodes.clear(); - transientNodes.addAll(set); - } - invalidateCache(false); - } - - public boolean removeIfTransient(Predicate predicate) { - boolean result; - - synchronized (nodes) { - result = transientNodes.removeIf(predicate); - } - - if (result) { - invalidateCache(false); - } - - return result; - } - - /** - * Combines and returns this holders nodes in a priority order. - * - * @param mergeTemp if the temporary nodes should be merged together with permanent nodes - * @return the holders transient and permanent nodes - */ - public SortedSet getPermissions(boolean mergeTemp) { - return mergeTemp ? mergedCache.get() : cache.get(); - } - - /** - * Resolves inherited nodes and returns them - * - * @param excludedGroups a list of groups to exclude - * @param contexts context to decide if groups should be applied - * @return a set of nodes - */ - public SortedSet getAllNodes(Collection excludedGroups, ExtractedContexts contexts) { - return getAllNodesCache.getUnchecked(GetAllNodesRequest.of(excludedGroups == null ? ImmutableSet.of() : ImmutableSet.copyOf(excludedGroups), contexts)); - } - - /** - * Gets all of the nodes that this holder has (and inherits), given the context - * - * @param contexts the context for this request - * @return a map of permissions - */ - public Set getAllNodesFiltered(ExtractedContexts contexts) { - return getAllNodesFilteredCache.getUnchecked(contexts); - } - - /** - * Converts the output of {@link #getAllNodesFiltered(ExtractedContexts)}, and expands shorthand perms - * - * @param context the context for this request - * @return a map of permissions - */ - public Map exportNodes(Contexts context, boolean lowerCase) { - return exportNodesCache.getUnchecked(ExportNodesHolder.of(context, lowerCase)); - } - - public MetaHolder accumulateMeta(MetaHolder holder, Set excludedGroups, ExtractedContexts contexts) { - if (holder == null) { - holder = new MetaHolder( + public MetaAccumulator accumulateMeta(MetaAccumulator accumulator, Set excludedGroups, ExtractedContexts context) { + if (accumulator == null) { + accumulator = new MetaAccumulator( plugin.getConfiguration().get(ConfigKeys.PREFIX_FORMATTING_OPTIONS).copy(), plugin.getConfiguration().get(ConfigKeys.SUFFIX_FORMATTING_OPTIONS).copy() ); @@ -501,62 +575,58 @@ public abstract class PermissionHolder { excludedGroups = new HashSet<>(); } - excludedGroups.add(getObjectName().toLowerCase()); - - Contexts context = contexts.getContexts(); - String server = contexts.getServer(); - String world = contexts.getWorld(); - - SortedSet all = new TreeSet<>((SortedSet) getPermissions(true)); - for (LocalizedNode ln : all) { - Node n = ln.getNode(); - - if (!n.getValue()) continue; - if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) continue; - if (!n.shouldApplyOnServer(server, context.isIncludeGlobal(), false)) continue; - if (!n.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), false)) continue; - if (!n.shouldApplyWithContext(contexts.getContextSet(), false)) continue; - - holder.accumulateNode(ln); + if (this instanceof Group) { + excludedGroups.add(getObjectName().toLowerCase()); } + Contexts contexts = context.getContexts(); + String server = context.getServer(); + String world = context.getWorld(); + + // screw effectively final + Set finalExcludedGroups = excludedGroups; + MetaAccumulator finalAccumulator = accumulator; + + flattenTransientNodesToList(context.getContextSet()).stream() + .filter(Node::getValue) + .filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix()) + .filter(n -> n.shouldApplyOnServer(server, contexts.isIncludeGlobal(), false)) + .filter(n -> n.shouldApplyOnWorld(world, contexts.isIncludeGlobalWorld(), false)) + .filter(n -> n.shouldApplyWithContext(context.getContextSet(), false)) + .forEach(n -> finalAccumulator.accumulateNode(ImmutableLocalizedNode.of(n, getObjectName()))); + + flattenNodesToList(context.getContextSet()).stream() + .filter(Node::getValue) + .filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix()) + .filter(n -> n.shouldApplyOnServer(server, contexts.isIncludeGlobal(), false)) + .filter(n -> n.shouldApplyOnWorld(world, contexts.isIncludeGlobalWorld(), false)) + .filter(n -> n.shouldApplyWithContext(context.getContextSet(), false)) + .forEach(n -> finalAccumulator.accumulateNode(ImmutableLocalizedNode.of(n, getObjectName()))); + OptionalInt w = getWeight(); if (w.isPresent()) { - holder.accumulateWeight(w.getAsInt()); + accumulator.accumulateWeight(w.getAsInt()); } - Set parents = all.stream() - .map(LocalizedNode::getNode) + mergePermissions().stream() .filter(Node::getValue) .filter(Node::isGroupNode) - .collect(Collectors.toSet()); + .filter(n -> n.shouldApplyOnServer(server, contexts.isApplyGlobalGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX))) + .filter(n -> n.shouldApplyOnWorld(world, contexts.isApplyGlobalWorldGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX))) + .filter(n -> n.shouldApplyWithContext(contexts.getContexts(), false)) + .map(Node::getGroupName) + .distinct() + .map(n -> Optional.ofNullable(plugin.getGroupManager().getIfLoaded(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(g -> !finalExcludedGroups.contains(g.getObjectName().toLowerCase())) + .sorted((o1, o2) -> { + int result = Integer.compare(o1.getWeight().orElse(0), o2.getWeight().orElse(0)); + return result == 1 ? -1 : 1; + }) + .forEach(group -> group.accumulateMeta(finalAccumulator, finalExcludedGroups, context)); - parents.removeIf(node -> - !node.shouldApplyOnServer(server, context.isApplyGlobalGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || - !node.shouldApplyOnWorld(world, context.isApplyGlobalWorldGroups(), plugin.getConfiguration().get(ConfigKeys.APPLYING_REGEX)) || - !node.shouldApplyWithContext(contexts.getContextSet(), false) - ); - - TreeSet> sortedParents = new TreeSet<>(Util.META_COMPARATOR.reversed()); - for (Node node : parents) { - Group group = plugin.getGroupManager().getIfLoaded(node.getGroupName()); - - if (group == null) { - continue; - } - - if (excludedGroups.contains(group.getObjectName().toLowerCase())) { - continue; - } - - sortedParents.add(Maps.immutableEntry(group.getWeight().orElse(0), group)); - } - - for (Map.Entry e : sortedParents) { - e.getValue().accumulateMeta(holder, excludedGroups, contexts); - } - - return holder; + return accumulator; } /** @@ -568,10 +638,10 @@ public abstract class PermissionHolder { boolean work = false; Set removed = new HashSet<>(); - ImmutableSet before = ImmutableSet.copyOf(getPermissions(true)); + ImmutableSet before = ImmutableSet.copyOf(mergePermissions()); synchronized (nodes) { - Iterator it = nodes.iterator(); + Iterator it = nodes.values().iterator(); while (it.hasNext()) { Node entry = it.next(); if (entry.hasExpired()) { @@ -583,12 +653,12 @@ public abstract class PermissionHolder { } if (work) { - invalidateCache(true); + invalidateCache(); work = false; } synchronized (transientNodes) { - Iterator it = transientNodes.iterator(); + Iterator it = transientNodes.values().iterator(); while (it.hasNext()) { Node entry = it.next(); if (entry.hasExpired()) { @@ -600,14 +670,14 @@ public abstract class PermissionHolder { } if (work) { - invalidateCache(false); + invalidateCache(); } if (removed.isEmpty()) { return false; } - ImmutableSet after = ImmutableSet.copyOf(getPermissions(true)); + ImmutableSet after = ImmutableSet.copyOf(mergePermissions()); for (Node r : removed) { plugin.getApiProvider().getEventFactory().handleNodeRemove(r, this, before, after); @@ -617,7 +687,7 @@ public abstract class PermissionHolder { } public Optional getAlmostEquals(Node node, boolean t) { - for (Node n : t ? getTransientNodes() : getNodes()) { + for (Node n : t ? getTransientNodes().values() : getNodes().values()) { if (n.almostEquals(node)) { return Optional.of(n); } @@ -672,7 +742,7 @@ public abstract class PermissionHolder { * @return the result of the lookup */ public InheritanceInfo inheritsPermissionInfo(Node node) { - for (LocalizedNode n : getAllNodes(null, ExtractedContexts.generate(Contexts.allowAll()))) { + for (LocalizedNode n : resolveInheritances(ExtractedContexts.generate(Contexts.allowAll()))) { if (n.getNode().almostEquals(node)) { return InheritanceInfo.of(n); } @@ -726,14 +796,14 @@ public abstract class PermissionHolder { throw new ObjectAlreadyHasException(); } - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - nodes.add(node); + nodes.put(node.getFullContexts().makeImmutable(), node); } - invalidateCache(true); + invalidateCache(); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeAdd(node, this, before, after); } @@ -765,16 +835,16 @@ public abstract class PermissionHolder { // Create a new node with the same properties, but add the expiry dates together Node newNode = NodeFactory.builderFromExisting(node).setExpiry(previous.getExpiryUnixTime() + node.getSecondsTilExpiry()).build(); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); // Remove the old node & add the new one. synchronized (nodes) { - nodes.remove(previous); - nodes.add(newNode); + nodes.remove(previous.getContexts().makeImmutable(), previous); + nodes.put(newNode.getFullContexts().makeImmutable(), newNode); } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeAdd(newNode, this, before, after); return newNode; } @@ -790,15 +860,15 @@ public abstract class PermissionHolder { // Only replace if the new expiry time is greater than the old one. if (node.getExpiryUnixTime() > previous.getExpiryUnixTime()) { - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - nodes.remove(previous); - nodes.add(node); + nodes.remove(previous.getFullContexts().makeImmutable(), previous); + nodes.put(node.getFullContexts().makeImmutable(), node); } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeAdd(node, this, before, after); return node; } @@ -830,14 +900,14 @@ public abstract class PermissionHolder { throw new ObjectAlreadyHasException(); } - ImmutableSet before = ImmutableSet.copyOf(getTransientNodes()); + ImmutableSet before = ImmutableSet.copyOf(getTransientNodes().values()); synchronized (transientNodes) { - transientNodes.add(node); + transientNodes.put(node.getFullContexts().makeImmutable(), node); } - invalidateCache(false); + invalidateCache(); - ImmutableSet after = ImmutableSet.copyOf(getTransientNodes()); + ImmutableSet after = ImmutableSet.copyOf(getTransientNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeAdd(node, this, before, after); } @@ -883,14 +953,14 @@ public abstract class PermissionHolder { throw new ObjectLacksException(); } - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - nodes.removeIf(e -> e.almostEquals(node)); + this.nodes.get(node.getFullContexts().makeImmutable()).removeIf(e -> e.almostEquals(node)); } - invalidateCache(true); + invalidateCache(); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeRemove(node, this, before, after); } @@ -907,14 +977,14 @@ public abstract class PermissionHolder { * @throws ObjectLacksException if the holder doesn't have this node already */ public void unsetPermissionExact(Node node) throws ObjectLacksException { - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - nodes.removeIf(e -> e.equals(node)); + nodes.get(node.getFullContexts().makeImmutable()).removeIf(e -> e.equals(node)); } - invalidateCache(true); + invalidateCache(); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); if (before.size() == after.size()) { throw new ObjectLacksException(); @@ -934,14 +1004,14 @@ public abstract class PermissionHolder { throw new ObjectLacksException(); } - ImmutableSet before = ImmutableSet.copyOf(getTransientNodes()); + ImmutableSet before = ImmutableSet.copyOf(getTransientNodes().values()); synchronized (transientNodes) { - transientNodes.removeIf(e -> e.almostEquals(node)); + transientNodes.get(node.getFullContexts().makeImmutable()).removeIf(e -> e.almostEquals(node)); } - invalidateCache(false); + invalidateCache(); - ImmutableSet after = ImmutableSet.copyOf(getTransientNodes()); + ImmutableSet after = ImmutableSet.copyOf(getTransientNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeRemove(node, this, before, after); } @@ -1063,26 +1133,26 @@ public abstract class PermissionHolder { * Clear all of the holders permission nodes */ public void clearNodes() { - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { nodes.clear(); } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } public void clearNodes(String server) { String finalServer = Optional.ofNullable(server).orElse("global"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - if (!nodes.removeIf(n -> n.getServer().orElse("global").equalsIgnoreCase(finalServer))) { + if (!nodes.values().removeIf(n -> n.getServer().orElse("global").equalsIgnoreCase(finalServer))) { return; } } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } @@ -1090,25 +1160,25 @@ public abstract class PermissionHolder { String finalServer = Optional.ofNullable(server).orElse("global"); String finalWorld = Optional.ofNullable(world).orElse("null"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(n -> + boolean b = nodes.values().removeIf(n -> n.getServer().orElse("global").equalsIgnoreCase(finalServer) && n.getWorld().orElse("null").equalsIgnoreCase(finalWorld)); if (!b) { return; } } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } public void clearParents() { - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(Node::isGroupNode); + boolean b = nodes.values().removeIf(Node::isGroupNode); if (!b) { return; } @@ -1116,17 +1186,17 @@ public abstract class PermissionHolder { if (this instanceof User) { plugin.getUserManager().giveDefaultIfNeeded((User) this, false); } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } public void clearParents(String server) { String finalServer = Optional.ofNullable(server).orElse("global"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(n -> + boolean b = nodes.values().removeIf(n -> n.isGroupNode() && n.getServer().orElse("global").equalsIgnoreCase(finalServer) ); if (!b) { @@ -1136,8 +1206,8 @@ public abstract class PermissionHolder { if (this instanceof User) { plugin.getUserManager().giveDefaultIfNeeded((User) this, false); } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } @@ -1145,9 +1215,9 @@ public abstract class PermissionHolder { String finalServer = Optional.ofNullable(server).orElse("global"); String finalWorld = Optional.ofNullable(world).orElse("null"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(n -> + boolean b = nodes.values().removeIf(n -> n.isGroupNode() && n.getServer().orElse("global").equalsIgnoreCase(finalServer) && n.getWorld().orElse("null").equalsIgnoreCase(finalWorld) @@ -1159,30 +1229,30 @@ public abstract class PermissionHolder { if (this instanceof User) { plugin.getUserManager().giveDefaultIfNeeded((User) this, false); } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } public void clearMeta() { - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - if (!nodes.removeIf(n -> n.isMeta() || n.isPrefix() || n.isSuffix())) { + if (!nodes.values().removeIf(n -> n.isMeta() || n.isPrefix() || n.isSuffix())) { return; } } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } public void clearMeta(String server) { String finalServer = Optional.ofNullable(server).orElse("global"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(n -> + boolean b = nodes.values().removeIf(n -> (n.isMeta() || n.isPrefix() || n.isSuffix()) && n.getServer().orElse("global").equalsIgnoreCase(finalServer) ); @@ -1190,8 +1260,8 @@ public abstract class PermissionHolder { return; } } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } @@ -1199,9 +1269,9 @@ public abstract class PermissionHolder { String finalServer = Optional.ofNullable(server).orElse("global"); String finalWorld = Optional.ofNullable(world).orElse("null"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(n -> + boolean b = nodes.values().removeIf(n -> (n.isMeta() || n.isPrefix() || n.isSuffix()) && ( n.getServer().orElse("global").equalsIgnoreCase(finalServer) && n.getWorld().orElse("null").equalsIgnoreCase(finalWorld) @@ -1211,8 +1281,8 @@ public abstract class PermissionHolder { return; } } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } @@ -1220,9 +1290,9 @@ public abstract class PermissionHolder { String finalServer = Optional.ofNullable(server).orElse("global"); String finalWorld = Optional.ofNullable(world).orElse("null"); - ImmutableSet before = ImmutableSet.copyOf(getNodes()); + ImmutableSet before = ImmutableSet.copyOf(getNodes().values()); synchronized (nodes) { - boolean b = nodes.removeIf(n -> + boolean b = nodes.values().removeIf(n -> n.isMeta() && (n.isTemporary() == temp) && n.getMeta().getKey().equalsIgnoreCase(key) && n.getServer().orElse("global").equalsIgnoreCase(finalServer) && n.getWorld().orElse("null").equalsIgnoreCase(finalWorld) @@ -1231,19 +1301,19 @@ public abstract class PermissionHolder { return; } } - invalidateCache(true); - ImmutableSet after = ImmutableSet.copyOf(getNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } public void clearTransientNodes() { - ImmutableSet before = ImmutableSet.copyOf(getTransientNodes()); + ImmutableSet before = ImmutableSet.copyOf(getTransientNodes().values()); synchronized (transientNodes) { transientNodes.clear(); } - invalidateCache(false); - ImmutableSet after = ImmutableSet.copyOf(getTransientNodes()); + invalidateCache(); + ImmutableSet after = ImmutableSet.copyOf(getTransientNodes().values()); plugin.getApiProvider().getEventFactory().handleNodeClear(this, before, after); } @@ -1251,26 +1321,26 @@ public abstract class PermissionHolder { * @return The temporary nodes held by the holder */ public Set getTemporaryNodes() { - return getPermissions(false).stream().filter(Node::isTemporary).collect(Collectors.toSet()); + return mergePermissionsToList().stream().filter(Node::isTemporary).collect(Collectors.toSet()); } /** * @return The permanent nodes held by the holder */ public Set getPermanentNodes() { - return getPermissions(false).stream().filter(Node::isPermanent).collect(Collectors.toSet()); + return mergePermissionsToList().stream().filter(Node::isPermanent).collect(Collectors.toSet()); } public Set getPrefixNodes() { - return getPermissions(false).stream().filter(Node::isPrefix).collect(Collectors.toSet()); + return mergePermissionsToList().stream().filter(Node::isPrefix).collect(Collectors.toSet()); } public Set getSuffixNodes() { - return getPermissions(false).stream().filter(Node::isSuffix).collect(Collectors.toSet()); + return mergePermissionsToList().stream().filter(Node::isSuffix).collect(Collectors.toSet()); } public Set getMetaNodes() { - return getPermissions(false).stream().filter(Node::isMeta).collect(Collectors.toSet()); + return mergePermissionsToList().stream().filter(Node::isMeta).collect(Collectors.toSet()); } public OptionalInt getWeight() { @@ -1278,7 +1348,7 @@ public abstract class PermissionHolder { OptionalInt weight = OptionalInt.empty(); try { - weight = getNodes().stream() + weight = mergePermissionsToList().stream() .filter(n -> n.getPermission().startsWith("weight.")) .map(n -> n.getPermission().substring("weight.".length())) .mapToInt(Integer::parseInt) @@ -1301,14 +1371,14 @@ public abstract class PermissionHolder { * @return a {@link List} of group names */ public List getGroupNames() { - return getNodes().stream() + return mergePermissionsToList().stream() .filter(Node::isGroupNode) .map(Node::getGroupName) .collect(Collectors.toList()); } public Set getGroupReferences() { - return getNodes().stream() + return mergePermissionsToList().stream() .filter(Node::isGroupNode) .map(Node::getGroupName) .map(String::toLowerCase) @@ -1324,7 +1394,7 @@ public abstract class PermissionHolder { * @return a {@link List} of group names */ public List getLocalGroups(String server, String world) { - return getNodes().stream() + return mergePermissionsToList().stream() .filter(Node::isGroupNode) .filter(n -> n.shouldApplyOnWorld(world, false, true)) .filter(n -> n.shouldApplyOnServer(server, false, true)) @@ -1333,7 +1403,7 @@ public abstract class PermissionHolder { } public List getLocalGroups(String server, String world, boolean includeGlobal) { - return getNodes().stream() + return mergePermissionsToList().stream() .filter(Node::isGroupNode) .filter(n -> n.shouldApplyOnWorld(world, includeGlobal, true)) .filter(n -> n.shouldApplyOnServer(server, includeGlobal, true)) @@ -1348,14 +1418,14 @@ public abstract class PermissionHolder { * @return a {@link List} of group names */ public List getLocalGroups(String server) { - return getNodes().stream() + return mergePermissionsToList().stream() .filter(Node::isGroupNode) .filter(n -> n.shouldApplyOnServer(server, false, true)) .map(Node::getGroupName) .collect(Collectors.toList()); } - public static Map exportToLegacy(Set nodes) { + public static Map exportToLegacy(Iterable nodes) { Map m = new HashMap<>(); for (Node node : nodes) { m.put(node.toSerializedNode(), node.getValue()); diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java index 4717181b..d61d53c4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java @@ -171,6 +171,5 @@ public class User extends PermissionHolder implements Identifiable im boolean hasGroup = false; if (user.getPrimaryGroup().getStoredValue() != null && !user.getPrimaryGroup().getStoredValue().isEmpty()) { - for (Node node : user.getPermissions(false)) { + for (Node node : user.getNodes().values()) { if (node.isServerSpecific() || node.isWorldSpecific()) { continue; } @@ -78,7 +78,7 @@ public class GenericUserManager extends AbstractManager im return true; } - for (Node node : user.getNodes()) { + for (Node node : user.getNodes().values()) { // There's only one. if (!node.isGroupNode()) { return true; diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java index af6a0df9..3b4ac9b9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java @@ -48,7 +48,7 @@ public class AllParentsByWeightHolder extends StoredHolder { return cachedValue; } - cachedValue = user.getAllNodes(null, ExtractedContexts.generate(Contexts.allowAll())).stream() + cachedValue = user.resolveInheritancesAlmostEqual(ExtractedContexts.generate(Contexts.allowAll())).stream() .filter(Node::isGroupNode) .filter(Node::getValue) .map(n -> Optional.ofNullable(user.getPlugin().getGroupManager().getIfLoaded(n.getGroupName()))) diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java index 0930e548..14ee179b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java @@ -46,7 +46,7 @@ public class ParentsByWeightHolder extends StoredHolder { return cachedValue; } - cachedValue = user.getPermissions(true).stream() + cachedValue = user.mergePermissionsToList().stream() .filter(Node::isGroupNode) .filter(Node::getValue) .map(n -> Optional.ofNullable(user.getPlugin().getGroupManager().getIfLoaded(n.getGroupName()))) diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java index 5ef74920..0e75161f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java @@ -171,7 +171,7 @@ public class JSONBacking extends FlatfileBacking { data.addProperty("name", user.getName()); data.addProperty("primaryGroup", user.getPrimaryGroup().getStoredValue()); - Set nodes = user.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set nodes = user.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); data.add("permissions", serializePermissions(nodes)); return writeElementToFile(userFile, data); @@ -270,7 +270,7 @@ public class JSONBacking extends FlatfileBacking { JsonObject data = new JsonObject(); data.addProperty("name", group.getName()); - Set nodes = group.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set nodes = group.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); data.add("permissions", serializePermissions(nodes)); return writeElementToFile(groupFile, data); @@ -321,7 +321,7 @@ public class JSONBacking extends FlatfileBacking { JsonObject data = new JsonObject(); data.addProperty("name", group.getName()); - Set nodes = group.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set nodes = group.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); data.add("permissions", serializePermissions(nodes)); return writeElementToFile(groupFile, data); }, false); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java index 2e4a839b..a9fa2d53 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java @@ -97,7 +97,7 @@ public class MongoDBBacking extends AbstractBacking { .append("primaryGroup", user.getPrimaryGroup().getStoredValue()); Document perms = new Document(); - for (Map.Entry e : convert(exportToLegacy(user.getNodes())).entrySet()) { + for (Map.Entry e : convert(exportToLegacy(user.getNodes().values())).entrySet()) { perms.append(e.getKey(), e.getValue()); } @@ -109,7 +109,7 @@ public class MongoDBBacking extends AbstractBacking { Document main = new Document("_id", group.getName()); Document perms = new Document(); - for (Map.Entry e : convert(exportToLegacy(group.getNodes())).entrySet()) { + for (Map.Entry e : convert(exportToLegacy(group.getNodes().values())).entrySet()) { perms.append(e.getKey(), e.getValue()); } @@ -237,7 +237,10 @@ public class MongoDBBacking extends AbstractBacking { if (cursor.hasNext()) { // User exists, let's load. Document d = cursor.next(); - user.setNodes(revert((Map) d.get("perms"))); + user.setNodes(revert((Map) d.get("perms")).entrySet().stream() + .map(e -> NodeFactory.fromSerialisedNode(e.getKey(), e.getValue())) + .collect(Collectors.toSet()) + ); user.getPrimaryGroup().setStoredValue(d.getString("primaryGroup")); boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); @@ -366,7 +369,10 @@ public class MongoDBBacking extends AbstractBacking { if (cursor.hasNext()) { // Group exists, let's load. Document d = cursor.next(); - group.setNodes(revert((Map) d.get("perms"))); + group.setNodes(revert((Map) d.get("perms")).entrySet().stream() + .map(e -> NodeFactory.fromSerialisedNode(e.getKey(), e.getValue())) + .collect(Collectors.toSet()) + ); } else { c.insertOne(fromGroup(group)); } @@ -389,7 +395,11 @@ public class MongoDBBacking extends AbstractBacking { try (MongoCursor cursor = c.find(new Document("_id", group.getName())).iterator()) { if (cursor.hasNext()) { Document d = cursor.next(); - group.setNodes(revert((Map) d.get("perms"))); + + group.setNodes(revert((Map) d.get("perms")).entrySet().stream() + .map(e -> NodeFactory.fromSerialisedNode(e.getKey(), e.getValue())) + .collect(Collectors.toSet()) + ); return true; } return false; diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java index f3bd4864..645ebbe1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java @@ -385,7 +385,7 @@ public class SQLBacking extends AbstractBacking { return false; } - Set local = user.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set local = user.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); Map.Entry, Set> diff = compareSets(local, remote); @@ -668,7 +668,7 @@ public class SQLBacking extends AbstractBacking { return false; } - Set local = group.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set local = group.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); Map.Entry, Set> diff = compareSets(local, remote); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java index cc734459..e7828d0c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java @@ -171,7 +171,7 @@ public class YAMLBacking extends FlatfileBacking { values.put("name", user.getName()); values.put("primary-group", user.getPrimaryGroup().getStoredValue()); - Set data = user.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set data = user.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); values.put("permissions", serializePermissions(data)); return writeMapToFile(userFile, values); @@ -268,7 +268,7 @@ public class YAMLBacking extends FlatfileBacking { Map values = new LinkedHashMap<>(); values.put("name", group.getName()); - Set data = group.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set data = group.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); values.put("permissions", serializePermissions(data)); return writeMapToFile(groupFile, values); } @@ -318,7 +318,7 @@ public class YAMLBacking extends FlatfileBacking { Map values = new LinkedHashMap<>(); values.put("name", group.getName()); - Set data = group.getNodes().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); + Set data = group.getNodes().values().stream().map(NodeDataHolder::fromNode).collect(Collectors.toSet()); values.put("permissions", serializePermissions(data)); return writeMapToFile(groupFile, values); }, false); diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/ExtractedContexts.java b/common/src/main/java/me/lucko/luckperms/common/utils/ExtractedContexts.java index c7176a09..f3a8bbe9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/utils/ExtractedContexts.java +++ b/common/src/main/java/me/lucko/luckperms/common/utils/ExtractedContexts.java @@ -29,7 +29,6 @@ import lombok.ToString; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.ImmutableContextSet; -import me.lucko.luckperms.api.context.MutableContextSet; @Getter @EqualsAndHashCode @@ -43,29 +42,26 @@ public class ExtractedContexts { return new ExtractedContexts(contexts); } - private Contexts contexts; - private ImmutableContextSet contextSet; + private final Contexts contexts; + private final ImmutableContextSet contextSet; private String server; private String world; private ExtractedContexts(Contexts context) { this.contexts = context; + this.contextSet = context.getContexts().makeImmutable(); setup(context.getContexts()); } private ExtractedContexts(ContextSet contexts) { this.contexts = null; + this.contextSet = contexts.makeImmutable(); setup(contexts); } private void setup(ContextSet contexts) { - MutableContextSet contextSet = MutableContextSet.fromSet(contexts); - server = contextSet.getValues("server").stream().findAny().orElse(null); - world = contextSet.getValues("world").stream().findAny().orElse(null); - contextSet.removeAll("server"); - contextSet.removeAll("world"); - - this.contextSet = contextSet.makeImmutable(); + server = contexts.getAnyValue("server").orElse(null); + world = contexts.getAnyValue("world").orElse(null); } public Contexts getContexts() { diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/NodeTools.java b/common/src/main/java/me/lucko/luckperms/common/utils/NodeTools.java new file mode 100644 index 00000000..1544a3b3 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/utils/NodeTools.java @@ -0,0 +1,103 @@ +/* + * 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.common.utils; + +import lombok.experimental.UtilityClass; + +import me.lucko.luckperms.api.Node; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@UtilityClass +public class NodeTools { + + public static void removeAlmostEqual(Iterator it) { + List alreadyIn = new ArrayList<>(); + + iter: + while (it.hasNext()) { + T next = it.next(); + for (T n : alreadyIn) { + if (next.almostEquals(n)) { + it.remove(); + continue iter; + } + } + + alreadyIn.add(next); + } + } + + public static void removeIgnoreValue(Iterator it) { + List alreadyIn = new ArrayList<>(); + + iter: + while (it.hasNext()) { + T next = it.next(); + for (T n : alreadyIn) { + if (next.equalsIgnoringValue(n)) { + it.remove(); + continue iter; + } + } + + alreadyIn.add(next); + } + } + + public static void removeIgnoreValueOrTemp(Iterator it) { + List alreadyIn = new ArrayList<>(); + + iter: + while (it.hasNext()) { + T next = it.next(); + for (T n : alreadyIn) { + if (next.equalsIgnoringValueOrTemp(n)) { + it.remove(); + continue iter; + } + } + + alreadyIn.add(next); + } + } + + public static void removeSamePermission(Iterator it) { + List alreadyIn = new ArrayList<>(); + + iter: + while (it.hasNext()) { + T next = it.next(); + for (T n : alreadyIn) { + if (next.getPermission().equals(n.getPermission())) { + it.remove(); + continue iter; + } + } + + alreadyIn.add(next); + } + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java index ad8089e7..efa768ea 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeGroup.java @@ -33,7 +33,7 @@ import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ContextSet; -import me.lucko.luckperms.common.caching.MetaHolder; +import me.lucko.luckperms.common.caching.MetaAccumulator; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.utils.ExtractedContexts; import me.lucko.luckperms.sponge.LPSpongePlugin; @@ -83,7 +83,7 @@ public class SpongeGroup extends Group { @Override public NodeTree load(ContextSet contexts) { // TODO move this away from NodeTree - Map permissions = parent.getAllNodesFiltered(ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))).stream() + Map permissions = parent.getAllNodes(ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))).stream() .map(LocalizedNode::getNode) .collect(Collectors.toMap(Node::getPermission, Node::getValue)); @@ -96,7 +96,7 @@ public class SpongeGroup extends Group { .build(new CacheLoader>() { @Override public Set load(ContextSet contexts) { - Set subjects = parent.getAllNodesFiltered(ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))).stream() + Set subjects = parent.getAllNodes(ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))).stream() .map(LocalizedNode::getNode) .filter(Node::isGroupNode) .map(Node::getGroupName) @@ -224,17 +224,17 @@ public class SpongeGroup extends Group { } private Optional getChatMeta(ContextSet contexts, boolean prefix) { - MetaHolder metaHolder = parent.accumulateMeta(null, null, ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))); + MetaAccumulator metaAccumulator = parent.accumulateMeta(null, null, ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))); if (prefix) { - return Optional.ofNullable(metaHolder.getPrefixStack().toFormattedString()); + return Optional.ofNullable(metaAccumulator.getPrefixStack().toFormattedString()); } else { - return Optional.ofNullable(metaHolder.getSuffixStack().toFormattedString()); + return Optional.ofNullable(metaAccumulator.getSuffixStack().toFormattedString()); } } private Optional getMeta(ContextSet contexts, String key) { - MetaHolder metaHolder = parent.accumulateMeta(null, null, ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))); - Map meta = metaHolder.getMeta(); + MetaAccumulator metaAccumulator = parent.accumulateMeta(null, null, ExtractedContexts.generate(plugin.getService().calculateContexts(contexts))); + Map meta = metaAccumulator.getMeta(); return Optional.ofNullable(meta.get(key)); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java index d930c749..851b1866 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/model/SpongeUser.java @@ -93,7 +93,7 @@ public class SpongeUser extends User { return (now - lastUse) > 600000; } - private void checkData() { + private synchronized void checkData() { if (parent.getUserData() == null) { plugin.getLog().warn("User " + parent.getName() + " - " + parent.getUuid() + " does not have any data loaded."); parent.setupData(false); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java index 443f80ff..3728c97a 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java @@ -33,7 +33,7 @@ import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.ImmutableContextSet; -import me.lucko.luckperms.common.caching.MetaHolder; +import me.lucko.luckperms.common.caching.MetaAccumulator; import me.lucko.luckperms.common.core.NodeBuilder; import me.lucko.luckperms.common.core.NodeFactory; import me.lucko.luckperms.common.core.model.Group; @@ -75,7 +75,7 @@ public class LuckPermsSubjectData implements LPSubjectData { try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_GET_PERMISSIONS)) { Map> perms = new HashMap<>(); - for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { + for (Node n : enduring ? holder.getNodes().values() : holder.getTransientNodes().values()) { ContextSet contexts = n.getFullContexts(); perms.computeIfAbsent(contexts.makeImmutable(), cs -> new HashMap<>()).put(n.getPermission(), n.getValue()); } @@ -166,7 +166,7 @@ public class LuckPermsSubjectData implements LPSubjectData { try (Timing ignored = service.getPlugin().getTimings().time(LPTiming.LP_SUBJECT_GET_PARENTS)) { Map> parents = new HashMap<>(); - for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { + for (Node n : enduring ? holder.getNodes().values() : holder.getTransientNodes().values()) { if (!n.isGroupNode()) continue; ContextSet contexts = n.getFullContexts(); @@ -271,7 +271,7 @@ public class LuckPermsSubjectData implements LPSubjectData { Map minPrefixPriority = new HashMap<>(); Map minSuffixPriority = new HashMap<>(); - for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { + for (Node n : enduring ? holder.getNodes().values() : holder.getTransientNodes().values()) { if (!n.getValue()) continue; if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) continue; @@ -331,8 +331,8 @@ public class LuckPermsSubjectData implements LPSubjectData { toRemove.forEach(makeUnsetConsumer(enduring)); - MetaHolder metaHolder = holder.accumulateMeta(null, null, ExtractedContexts.generate(service.calculateContexts(context))); - int priority = (type.equals("prefix") ? metaHolder.getPrefixes() : metaHolder.getSuffixes()).keySet().stream() + MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, ExtractedContexts.generate(service.calculateContexts(context))); + int priority = (type.equals("prefix") ? metaAccumulator.getPrefixes() : metaAccumulator.getSuffixes()).keySet().stream() .mapToInt(e -> e).max().orElse(0); priority += 10; @@ -416,7 +416,7 @@ public class LuckPermsSubjectData implements LPSubjectData { } private Stream streamNodes(boolean enduring) { - return (enduring ? holder.getNodes() : holder.getTransientNodes()).stream(); + return (enduring ? holder.getNodes() : holder.getTransientNodes()).values().stream(); } private Consumer makeUnsetConsumer(boolean enduring) {