diff --git a/api/src/main/java/net/luckperms/api/model/PermissionHolder.java b/api/src/main/java/net/luckperms/api/model/PermissionHolder.java index 47fea0e7e..36cb8bd45 100644 --- a/api/src/main/java/net/luckperms/api/model/PermissionHolder.java +++ b/api/src/main/java/net/luckperms/api/model/PermissionHolder.java @@ -31,13 +31,17 @@ import net.luckperms.api.model.data.NodeMap; import net.luckperms.api.model.group.Group; import net.luckperms.api.model.user.User; import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; import net.luckperms.api.query.QueryOptions; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.SortedSet; import java.util.UUID; +import java.util.stream.Collectors; /** * Generic superinterface for an object which holds permissions. @@ -121,7 +125,7 @@ public interface PermissionHolder { * @param dataType the data type * @return the data */ - NodeMap getData(@NonNull DataType dataType); + @NonNull NodeMap getData(@NonNull DataType dataType); /** * Gets the holders {@link DataType#NORMAL} data. @@ -149,35 +153,60 @@ public interface PermissionHolder { @NonNull NodeMap transientData(); /** - * Gets a flattened/squashed view of the holders permissions. + * Gets a flattened view of the holders own {@link Node}s. * - *

This list is constructed using the values - * of both the transient and enduring backing multimaps.

+ *

This list is constructed using the values of both the {@link #data() normal} + * and {@link #transientData() transient} backing node maps.

* - *

This means that it may contain duplicate entries.

- * - *

Use {@link #getDistinctNodes()} for a view without duplicates.

+ *

It may contain duplicate entries if the same node is added to both the normal + * and transient node maps. You can use {@link #getDistinctNodes()} for a view without + * duplicates.

* *

This method does not resolve inheritance rules.

* - * @return a list of the holders own nodes. + * @return a collection of the holders own nodes. */ - @NonNull Collection getNodes(); + default @NonNull Collection getNodes() { + /* This default method is overridden in the implementation, and is just here + to demonstrate what this method does in the API sources. */ + List nodes = new ArrayList<>(); + nodes.addAll(data().toCollection()); + nodes.addAll(transientData().toCollection()); + return nodes; + } /** - * Gets a sorted set of all held nodes. + * Gets a flattened view of the holders own {@link Node}s of the given {@code type}. + * + * @param type the type of node to filter by + * @param the node type + * @return a filtered collection of the holders own nodes + * @see #getNodes() + * @since 5.1 + */ + default @NonNull Collection getNodes(@NonNull NodeType type) { + /* This default method is overridden in the implementation, and is just here + to demonstrate what this method does in the API sources. */ + return getNodes().stream() + .filter(type::matches) + .map(type::cast) + .collect(Collectors.toList()); + } + + /** + * Gets a flattened and sorted view of the holders own distinct {@link Node}s. * *

Effectively a sorted version of {@link #getNodes()}, without duplicates. Use the * aforementioned method if you don't require either of these attributes.

* *

This method does not resolve inheritance rules.

* - * @return an immutable set of permissions in priority order + * @return a sorted set of the holders own distinct nodes */ @NonNull SortedSet getDistinctNodes(); /** - * Recursively resolves this holders permissions. + * Gets a resolved view of the holders own and inherited {@link Node}s. * *

The returned list will contain every inherited * node the holder has, in the order that they were inherited in.

@@ -189,22 +218,62 @@ public interface PermissionHolder { * with the entries from the end of the inheritance tree appearing last.

* * @param queryOptions the query options - * @return a list of nodes + * @return a list of the holders inherited nodes */ @NonNull Collection resolveInheritedNodes(@NonNull QueryOptions queryOptions); /** - * Gets a mutable sorted set of the nodes that this object has and inherits, filtered by context + * Gets a resolved view of the holders own and inherited {@link Node}s of a given {@code type}. * - *

Nodes are sorted into priority order. The order of inheritance is only important during - * the process of flattening inherited entries.

+ * @param type the type of node to filter by + * @param queryOptions the query options + * @param the node type + * @return a filtered list of the holders inherited nodes + * @see #resolveInheritedNodes(QueryOptions) + * @since 5.1 + */ + default @NonNull Collection resolveInheritedNodes(@NonNull NodeType type, @NonNull QueryOptions queryOptions) { + /* This default method is overridden in the implementation, and is just here + to demonstrate what this method does in the API sources. */ + return resolveInheritedNodes(queryOptions).stream() + .filter(type::matches) + .map(type::cast) + .collect(Collectors.toList()); + } + + /** + * Gets a resolved and sorted view of the holders own and inherited distinct {@link Node}s. + * + *

Effectively a sorted version of {@link #resolveInheritedNodes(QueryOptions)}, + * without duplicates. Use the aforementioned method if you don't require either of these + * attributes.

+ * + *

Inheritance is performed according to the platforms rules, and the order will vary + * depending on the accumulation order. By default, the holders own nodes are first in the list, + * with the entries from the end of the inheritance tree appearing last.

* * @param queryOptions the query options - * @return an immutable sorted set of permissions - * @throws NullPointerException if the context is null + * @return a sorted set of the holders distinct inherited nodes */ @NonNull SortedSet resolveDistinctInheritedNodes(@NonNull QueryOptions queryOptions); + /** + * Gets a collection of the {@link Group}s this holder inherits nodes from, both directly + * and indirectly (through directly inherited groups). + * + *

It effectively resolves the whole "inheritance tree".

+ * + *

The collection will be ordered according to the platforms inheritance rules. The groups + * which are inherited from first will appear earlier in the list.

+ * + *

The list will not contain the holder.

+ * + * @param queryOptions the query options + * @return a collection of the groups the holder inherits from + * @since 5.1 + */ + @NonNull Collection getInheritedGroups(@NonNull QueryOptions queryOptions); + /** * Removes any temporary permissions that have expired. * diff --git a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java index b4282d094..781381486 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/implementation/ApiPermissionHolder.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.common.api.implementation; import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.query.QueryOptionsImpl; +import me.lucko.luckperms.common.util.ImmutableCollectors; import net.luckperms.api.cacheddata.CachedDataManager; import net.luckperms.api.context.ContextSet; @@ -35,8 +36,10 @@ import net.luckperms.api.model.data.DataMutateResult; import net.luckperms.api.model.data.DataType; import net.luckperms.api.model.data.NodeMap; import net.luckperms.api.model.data.TemporaryNodeMergeStrategy; +import net.luckperms.api.model.group.Group; import net.luckperms.api.node.Node; import net.luckperms.api.node.NodeEqualityPredicate; +import net.luckperms.api.node.NodeType; import net.luckperms.api.query.QueryOptions; import net.luckperms.api.util.Tristate; @@ -88,7 +91,7 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo } @Override - public NodeMap getData(@NonNull DataType dataType) { + public @NonNull NodeMap getData(@NonNull DataType dataType) { switch (dataType) { case NORMAL: return this.normalData; @@ -114,6 +117,12 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo return this.handle.getOwnNodes(QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL); } + @Override + public @NonNull Collection getNodes(@NonNull NodeType type) { + Objects.requireNonNull(type, "type"); + return this.handle.getOwnNodes(type, QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL); + } + @Override public @NonNull SortedSet getDistinctNodes() { return this.handle.getOwnNodesSorted(QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL); @@ -121,14 +130,31 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public @NonNull List resolveInheritedNodes(@NonNull QueryOptions queryOptions) { + Objects.requireNonNull(queryOptions, "queryOptions"); return this.handle.resolveInheritedNodes(queryOptions); } + @Override + public @NonNull Collection resolveInheritedNodes(@NonNull NodeType type, @NonNull QueryOptions queryOptions) { + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(queryOptions, "queryOptions"); + return this.handle.resolveInheritedNodes(type, queryOptions); + } + @Override public @NonNull SortedSet resolveDistinctInheritedNodes(@NonNull QueryOptions queryOptions) { + Objects.requireNonNull(queryOptions, "queryOptions"); return this.handle.resolveInheritedNodesSorted(queryOptions); } + @Override + public @NonNull Collection getInheritedGroups(@NonNull QueryOptions queryOptions) { + Objects.requireNonNull(queryOptions, "queryOptions"); + return this.handle.resolveInheritanceTree(queryOptions).stream() + .map(me.lucko.luckperms.common.model.Group::getApiProxy) + .collect(ImmutableCollectors.toList()); + } + @Override public void auditTemporaryNodes() { this.handle.auditTemporaryNodes(); @@ -153,11 +179,14 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public @NonNull Tristate contains(@NonNull Node node, @NonNull NodeEqualityPredicate equalityPredicate) { + Objects.requireNonNull(node, "node"); + Objects.requireNonNull(equalityPredicate, "equalityPredicate"); return ApiPermissionHolder.this.handle.hasNode(this.dataType, node, equalityPredicate); } @Override public @NonNull DataMutateResult add(@NonNull Node node) { + Objects.requireNonNull(node, "node"); DataMutateResult result = ApiPermissionHolder.this.handle.setNode(this.dataType, node, true); if (result.wasSuccessful()) { onNodeChange(); @@ -167,6 +196,8 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public DataMutateResult.@NonNull WithMergedNode add(@NonNull Node node, @NonNull TemporaryNodeMergeStrategy temporaryNodeMergeStrategy) { + Objects.requireNonNull(node, "node"); + Objects.requireNonNull(temporaryNodeMergeStrategy, "temporaryNodeMergeStrategy"); DataMutateResult.WithMergedNode result = ApiPermissionHolder.this.handle.setNode(this.dataType, node, temporaryNodeMergeStrategy); if (result.getResult().wasSuccessful()) { onNodeChange(); @@ -176,6 +207,7 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public @NonNull DataMutateResult remove(@NonNull Node node) { + Objects.requireNonNull(node, "node"); DataMutateResult result = ApiPermissionHolder.this.handle.unsetNode(this.dataType, node); if (result.wasSuccessful()) { onNodeChange(); @@ -192,6 +224,7 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public void clear(@NonNull Predicate test) { + Objects.requireNonNull(test, "test"); if (ApiPermissionHolder.this.handle.removeIf(this.dataType, null, test, true)) { onNodeChange(); } @@ -200,6 +233,7 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public void clear(@NonNull ContextSet contextSet) { + Objects.requireNonNull(contextSet, "contextSet"); if (ApiPermissionHolder.this.handle.clearNodes(this.dataType, contextSet, true)) { onNodeChange(); } @@ -207,6 +241,8 @@ public class ApiPermissionHolder implements net.luckperms.api.model.PermissionHo @Override public void clear(@NonNull ContextSet contextSet, @NonNull Predicate test) { + Objects.requireNonNull(contextSet, "contextSet"); + Objects.requireNonNull(test, "test"); if (ApiPermissionHolder.this.handle.removeIf(this.dataType, contextSet, test, true)) { onNodeChange(); } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java index d1102d55f..affdabb6c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java @@ -41,6 +41,7 @@ import net.luckperms.api.context.DefaultContextKeys; import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.node.Node; import net.luckperms.api.node.NodeEqualityPredicate; +import net.luckperms.api.node.NodeType; import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata; import net.luckperms.api.node.types.InheritanceNode; import net.luckperms.api.query.Flag; @@ -63,6 +64,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListSet; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -152,19 +154,69 @@ public final class NodeMap { !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet); } + public void forEach(QueryOptions filter, Consumer consumer) { + for (Map.Entry> e : this.map.entrySet()) { + if (!filter.satisfies(e.getKey())) { + continue; + } + + if (normalNodesExcludeTest(filter, e.getKey())) { + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + // only copy inheritance nodes. + SortedSet inheritanceNodes = this.inheritanceMap.get(e.getKey()); + if (inheritanceNodes != null) { + inheritanceNodes.forEach(consumer); + } + } + } else { + e.getValue().forEach(consumer); + } + } + } + public void copyTo(Collection collection, QueryOptions filter) { for (Map.Entry> e : this.map.entrySet()) { - if (filter.satisfies(e.getKey())) { - if (normalNodesExcludeTest(filter, e.getKey())) { - if (inheritanceNodesIncludeTest(filter, e.getKey())) { - // only copy inheritance nodes. + if (!filter.satisfies(e.getKey())) { + continue; + } + + if (normalNodesExcludeTest(filter, e.getKey())) { + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + // only copy inheritance nodes. + SortedSet inheritanceNodes = this.inheritanceMap.get(e.getKey()); + if (inheritanceNodes != null) { + collection.addAll(inheritanceNodes); + } + } + } else { + collection.addAll(e.getValue()); + } + } + } + + public void copyTo(Collection collection, NodeType type, QueryOptions filter) { + for (Map.Entry> e : this.map.entrySet()) { + if (!filter.satisfies(e.getKey())) { + continue; + } + + if (normalNodesExcludeTest(filter, e.getKey())) { + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + // only copy inheritance nodes. + if (type == NodeType.INHERITANCE) { SortedSet inheritanceNodes = this.inheritanceMap.get(e.getKey()); if (inheritanceNodes != null) { - collection.addAll(inheritanceNodes); + for (InheritanceNode node : inheritanceNodes) { + collection.add(type.cast(node)); + } } } - } else { - collection.addAll(e.getValue()); + } + } else { + for (Node node : e.getValue()) { + if (type.matches(node)) { + collection.add(type.cast(node)); + } } } } @@ -172,10 +224,12 @@ public final class NodeMap { public void copyInheritanceNodesTo(Collection collection, QueryOptions filter) { for (Map.Entry> e : this.inheritanceMap.entrySet()) { - if (filter.satisfies(e.getKey())) { - if (inheritanceNodesIncludeTest(filter, e.getKey())) { - collection.addAll(e.getValue()); - } + if (!filter.satisfies(e.getKey())) { + continue; + } + + if (inheritanceNodesIncludeTest(filter, e.getKey())) { + collection.addAll(e.getValue()); } } } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index f21a66df5..545e29975 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.common.model; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import me.lucko.luckperms.common.cacheddata.HolderCachedDataManager; @@ -59,6 +60,7 @@ import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -249,73 +251,124 @@ public abstract class PermissionHolder { invalidateCache(); } - public List getOwnNodes(QueryOptions queryOptions) { - List nodes = new ArrayList<>(); - + private List queryOrder(QueryOptions queryOptions) { Comparator comparator = queryOptions.option(DataQueryOrderFunction.KEY) .map(func -> func.getOrderComparator(getIdentifier())) .orElse(DataQueryOrder.TRANSIENT_FIRST); - for (DataType dataType : DataQueryOrder.order(comparator)) { + return DataQueryOrder.order(comparator); + } + + public List getOwnNodes(QueryOptions queryOptions) { + List nodes = new ArrayList<>(); + for (DataType dataType : queryOrder(queryOptions)) { getData(dataType).copyTo(nodes, queryOptions); } - return nodes; } public SortedSet getOwnNodesSorted(QueryOptions queryOptions) { SortedSet nodes = new TreeSet<>(NodeWithContextComparator.reverse()); - - Comparator comparator = queryOptions.option(DataQueryOrderFunction.KEY) - .map(func -> func.getOrderComparator(getIdentifier())) - .orElse(DataQueryOrder.TRANSIENT_FIRST); - - for (DataType dataType : DataQueryOrder.order(comparator)) { + for (DataType dataType : queryOrder(queryOptions)) { getData(dataType).copyTo(nodes, queryOptions); } - return nodes; } public List getOwnInheritanceNodes(QueryOptions queryOptions) { List nodes = new ArrayList<>(); - - Comparator comparator = queryOptions.option(DataQueryOrderFunction.KEY) - .map(func -> func.getOrderComparator(getIdentifier())) - .orElse(DataQueryOrder.TRANSIENT_FIRST); - - for (DataType dataType : DataQueryOrder.order(comparator)) { + for (DataType dataType : queryOrder(queryOptions)) { getData(dataType).copyInheritanceNodesTo(nodes, queryOptions); } - return nodes; } - private void accumulateInheritedNodesTo(Collection accumulator, QueryOptions queryOptions) { - if (queryOptions.flag(Flag.RESOLVE_INHERITANCE)) { - InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions); - Iterable traversal = graph.traverse(this); - for (PermissionHolder holder : traversal) { - List nodes = holder.getOwnNodes(queryOptions); - accumulator.addAll(nodes); - } - } else { - accumulator.addAll(getOwnNodes(queryOptions)); + public List getOwnNodes(NodeType type, QueryOptions queryOptions) { + List nodes = new ArrayList<>(); + for (DataType dataType : queryOrder(queryOptions)) { + getData(dataType).copyTo(nodes, type, queryOptions); } + return nodes; } public List resolveInheritedNodes(QueryOptions queryOptions) { + if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) { + return getOwnNodes(queryOptions); + } + List nodes = new ArrayList<>(); - accumulateInheritedNodesTo(nodes, queryOptions); + InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions); + for (PermissionHolder holder : graph.traverse(this)) { + for (DataType dataType : holder.queryOrder(queryOptions)) { + holder.getData(dataType).copyTo(nodes, queryOptions); + } + } return nodes; + } public SortedSet resolveInheritedNodesSorted(QueryOptions queryOptions) { + if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) { + return getOwnNodesSorted(queryOptions); + } + SortedSet nodes = new TreeSet<>(NodeWithContextComparator.reverse()); - accumulateInheritedNodesTo(nodes, queryOptions); + InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions); + for (PermissionHolder holder : graph.traverse(this)) { + for (DataType dataType : holder.queryOrder(queryOptions)) { + holder.getData(dataType).copyTo(nodes, queryOptions); + } + } return nodes; } + public List resolveInheritedNodes(NodeType type, QueryOptions queryOptions) { + if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) { + return getOwnNodes(type, queryOptions); + } + + List nodes = new ArrayList<>(); + InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions); + for (PermissionHolder holder : graph.traverse(this)) { + for (DataType dataType : holder.queryOrder(queryOptions)) { + holder.getData(dataType).copyTo(nodes, type, queryOptions); + } + } + return nodes; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public List resolveInheritanceTree(QueryOptions queryOptions) { + if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) { + return Collections.emptyList(); + } + + InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions); + + // perform a full traversal of the inheritance tree + List traversal = new ArrayList<>(); + Iterables.addAll(traversal, graph.traverse(this)); + + // remove 'this' (the start node) - will usually be at traversal[0], + // but not always due to the possibility of post-traversal sorts! + if (traversal.get(0) == this) { + traversal.remove(0); + } else { + traversal.remove(this); + } + + // ensure our traversal now only consists of groups + for (PermissionHolder permissionHolder : traversal) { + if (!(permissionHolder instanceof Group)) { + throw new IllegalStateException("Non-group object in inheritance tree: " + permissionHolder); + } + } + + // cast List to List + // this feels a bit dirty but it works & avoids needless copying! + return (List) traversal; + } + public Map exportPermissions(QueryOptions queryOptions, boolean convertToLowercase, boolean resolveShorthand) { List entries = resolveInheritedNodes(queryOptions); return processExportedPermissions(entries, convertToLowercase, resolveShorthand); @@ -353,22 +406,24 @@ public abstract class PermissionHolder { public MetaAccumulator accumulateMeta(MetaAccumulator accumulator, QueryOptions queryOptions) { InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions); - Iterable traversal = graph.traverse(this); - for (PermissionHolder holder : traversal) { - List nodes = holder.getOwnNodes(queryOptions); - for (Node node : nodes) { - if (!node.getValue()) continue; - if (!NodeType.META_OR_CHAT_META.matches(node)) continue; - - accumulator.accumulateNode(node); + for (PermissionHolder holder : graph.traverse(this)) { + // accumulate nodes + for (DataType dataType : holder.queryOrder(queryOptions)) { + holder.getData(dataType).forEach(queryOptions, node -> { + if (node.getValue() && NodeType.META_OR_CHAT_META.matches(node)) { + accumulator.accumulateNode(node); + } + }); } + // accumulate weight OptionalInt w = holder.getWeight(); if (w.isPresent()) { accumulator.accumulateWeight(w.getAsInt()); } } + // accumulate primary group if (this instanceof User) { String primaryGroup = ((User) this).getPrimaryGroup().calculateValue(queryOptions); accumulator.setPrimaryGroup(primaryGroup);