Add getInheritedGroups and getNodes filtered by type methods to the API (#1926)

This commit is contained in:
Luck 2020-05-10 01:19:43 +01:00
parent 7845d89f10
commit 52731fe68e
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
4 changed files with 283 additions and 69 deletions

View File

@ -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.
*
* <p>This list is constructed using the values
* of both the transient and enduring backing multimaps.</p>
* <p>This list is constructed using the values of both the {@link #data() normal}
* and {@link #transientData() transient} backing node maps.</p>
*
* <p>This means that it <b>may contain</b> duplicate entries.</p>
*
* <p>Use {@link #getDistinctNodes()} for a view without duplicates.</p>
* <p>It <b>may contain</b> 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.</p>
*
* <p>This method <b>does not</b> resolve inheritance rules.</p>
*
* @return a list of the holders own nodes.
* @return a collection of the holders own nodes.
*/
@NonNull Collection<Node> getNodes();
default @NonNull Collection<Node> getNodes() {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
List<Node> 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 <T> the node type
* @return a filtered collection of the holders own nodes
* @see #getNodes()
* @since 5.1
*/
default <T extends Node> @NonNull Collection<T> getNodes(@NonNull NodeType<T> 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.
*
* <p>Effectively a sorted version of {@link #getNodes()}, without duplicates. Use the
* aforementioned method if you don't require either of these attributes.</p>
*
* <p>This method <b>does not</b> resolve inheritance rules.</p>
*
* @return an immutable set of permissions in priority order
* @return a sorted set of the holders own distinct nodes
*/
@NonNull SortedSet<Node> getDistinctNodes();
/**
* Recursively resolves this holders permissions.
* Gets a resolved view of the holders own and inherited {@link Node}s.
*
* <p>The returned list will contain every inherited
* node the holder has, in the order that they were inherited in.</p>
@ -189,22 +218,62 @@ public interface PermissionHolder {
* with the entries from the end of the inheritance tree appearing last.</p>
*
* @param queryOptions the query options
* @return a list of nodes
* @return a list of the holders inherited nodes
*/
@NonNull Collection<Node> 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}.
*
* <p>Nodes are sorted into priority order. The order of inheritance is only important during
* the process of flattening inherited entries.</p>
* @param type the type of node to filter by
* @param queryOptions the query options
* @param <T> the node type
* @return a filtered list of the holders inherited nodes
* @see #resolveInheritedNodes(QueryOptions)
* @since 5.1
*/
default <T extends Node> @NonNull Collection<T> resolveInheritedNodes(@NonNull NodeType<T> 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.
*
* <p>Effectively a sorted version of {@link #resolveInheritedNodes(QueryOptions)},
* without duplicates. Use the aforementioned method if you don't require either of these
* attributes.</p>
*
* <p>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.</p>
*
* @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<Node> 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).
*
* <p>It effectively resolves the whole "inheritance tree".</p>
*
* <p>The collection will be ordered according to the platforms inheritance rules. The groups
* which are inherited from first will appear earlier in the list.</p>
*
* <p>The list will not contain the holder.</p>
*
* @param queryOptions the query options
* @return a collection of the groups the holder inherits from
* @since 5.1
*/
@NonNull Collection<Group> getInheritedGroups(@NonNull QueryOptions queryOptions);
/**
* Removes any temporary permissions that have expired.
*

View File

@ -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 <T extends Node> Collection<T> getNodes(@NonNull NodeType<T> type) {
Objects.requireNonNull(type, "type");
return this.handle.getOwnNodes(type, QueryOptionsImpl.DEFAULT_NON_CONTEXTUAL);
}
@Override
public @NonNull SortedSet<Node> 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<Node> resolveInheritedNodes(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
return this.handle.resolveInheritedNodes(queryOptions);
}
@Override
public @NonNull <T extends Node> Collection<T> resolveInheritedNodes(@NonNull NodeType<T> type, @NonNull QueryOptions queryOptions) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(queryOptions, "queryOptions");
return this.handle.resolveInheritedNodes(type, queryOptions);
}
@Override
public @NonNull SortedSet<Node> resolveDistinctInheritedNodes(@NonNull QueryOptions queryOptions) {
Objects.requireNonNull(queryOptions, "queryOptions");
return this.handle.resolveInheritedNodesSorted(queryOptions);
}
@Override
public @NonNull Collection<Group> 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<? super Node> 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<? super Node> test) {
Objects.requireNonNull(contextSet, "contextSet");
Objects.requireNonNull(test, "test");
if (ApiPermissionHolder.this.handle.removeIf(this.dataType, contextSet, test, true)) {
onNodeChange();
}

View File

@ -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<? super Node> consumer) {
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> 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<InheritanceNode> inheritanceNodes = this.inheritanceMap.get(e.getKey());
if (inheritanceNodes != null) {
inheritanceNodes.forEach(consumer);
}
}
} else {
e.getValue().forEach(consumer);
}
}
}
public void copyTo(Collection<? super Node> collection, QueryOptions filter) {
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> 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<InheritanceNode> inheritanceNodes = this.inheritanceMap.get(e.getKey());
if (inheritanceNodes != null) {
collection.addAll(inheritanceNodes);
}
}
} else {
collection.addAll(e.getValue());
}
}
}
public <T extends Node> void copyTo(Collection<? super T> collection, NodeType<T> type, QueryOptions filter) {
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> 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<InheritanceNode> 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<? super InheritanceNode> collection, QueryOptions filter) {
for (Map.Entry<ImmutableContextSet, SortedSet<InheritanceNode>> 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());
}
}
}

View File

@ -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<Node> getOwnNodes(QueryOptions queryOptions) {
List<Node> nodes = new ArrayList<>();
private List<DataType> queryOrder(QueryOptions queryOptions) {
Comparator<DataType> 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<Node> getOwnNodes(QueryOptions queryOptions) {
List<Node> nodes = new ArrayList<>();
for (DataType dataType : queryOrder(queryOptions)) {
getData(dataType).copyTo(nodes, queryOptions);
}
return nodes;
}
public SortedSet<Node> getOwnNodesSorted(QueryOptions queryOptions) {
SortedSet<Node> nodes = new TreeSet<>(NodeWithContextComparator.reverse());
Comparator<DataType> 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<InheritanceNode> getOwnInheritanceNodes(QueryOptions queryOptions) {
List<InheritanceNode> nodes = new ArrayList<>();
Comparator<DataType> 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<Node> accumulator, QueryOptions queryOptions) {
if (queryOptions.flag(Flag.RESOLVE_INHERITANCE)) {
InheritanceGraph graph = this.plugin.getInheritanceGraphFactory().getGraph(queryOptions);
Iterable<PermissionHolder> traversal = graph.traverse(this);
for (PermissionHolder holder : traversal) {
List<? extends Node> nodes = holder.getOwnNodes(queryOptions);
accumulator.addAll(nodes);
}
} else {
accumulator.addAll(getOwnNodes(queryOptions));
public <T extends Node> List<T> getOwnNodes(NodeType<T> type, QueryOptions queryOptions) {
List<T> nodes = new ArrayList<>();
for (DataType dataType : queryOrder(queryOptions)) {
getData(dataType).copyTo(nodes, type, queryOptions);
}
return nodes;
}
public List<Node> resolveInheritedNodes(QueryOptions queryOptions) {
if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) {
return getOwnNodes(queryOptions);
}
List<Node> 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<Node> resolveInheritedNodesSorted(QueryOptions queryOptions) {
if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) {
return getOwnNodesSorted(queryOptions);
}
SortedSet<Node> 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 <T extends Node> List<T> resolveInheritedNodes(NodeType<T> type, QueryOptions queryOptions) {
if (!queryOptions.flag(Flag.RESOLVE_INHERITANCE)) {
return getOwnNodes(type, queryOptions);
}
List<T> 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<Group> 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<PermissionHolder> 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<PermissionHolder> to List<Group>
// this feels a bit dirty but it works & avoids needless copying!
return (List) traversal;
}
public Map<String, Boolean> exportPermissions(QueryOptions queryOptions, boolean convertToLowercase, boolean resolveShorthand) {
List<Node> 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<PermissionHolder> traversal = graph.traverse(this);
for (PermissionHolder holder : traversal) {
List<? extends Node> 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);