1
0
mirror of https://github.com/lucko/LuckPerms.git synced 2025-09-24 21:11:41 +02:00

Abstract out the process of traversing the inheritance tree, add configurable option to choose which algorithm to use (#719)

This commit is contained in:
Luck
2018-02-16 17:46:17 +00:00
parent f1047df98e
commit 0adf85746d
22 changed files with 808 additions and 295 deletions

View File

@@ -63,6 +63,7 @@ import me.lucko.luckperms.common.dependencies.DependencyRegistry;
import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.inheritance.InheritanceHandler;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
import me.lucko.luckperms.common.locale.SimpleLocaleManager;
@@ -131,6 +132,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
private LocaleManager localeManager;
private PluginClassLoader pluginClassLoader;
private DependencyManager dependencyManager;
private InheritanceHandler inheritanceHandler;
private CachedStateManager cachedStateManager;
private ContextManager<Player> contextManager;
private CalculatorFactory calculatorFactory;
@@ -233,6 +235,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
// load internal managers
getLog().info("Loading internal permission managers...");
this.inheritanceHandler = new InheritanceHandler(this);
this.userManager = new StandardUserManager(this);
this.groupManager = new StandardGroupManager(this);
this.trackManager = new StandardTrackManager(this);
@@ -673,6 +676,11 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin {
return this.contextManager;
}
@Override
public InheritanceHandler getInheritanceHandler() {
return this.inheritanceHandler;
}
@Override
public CalculatorFactory getCalculatorFactory() {
return this.calculatorFactory;

View File

@@ -279,7 +279,7 @@ public class VaultChatHook extends AbstractVaultChat {
}
// find the max inherited priority & add 10
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, null, createContextForWorldSet(world));
MetaAccumulator metaAccumulator = holder.accumulateMeta(null, createContextForWorldSet(world));
int priority = (type == ChatMetaType.PREFIX ? metaAccumulator.getPrefixes() : metaAccumulator.getSuffixes()).keySet().stream()
.mapToInt(e -> e).max().orElse(0) + 10;

View File

@@ -134,6 +134,18 @@ apply-bukkit-default-permissions: true
# permissions when considering if a player should have access to a certain permission.
apply-bukkit-attachment-permissions: true
# The algorithm LuckPerms should use when traversing the "inheritance tree".
#
# The valid options are:
# - breadth-first
# - depth-first-pre-order
# - depth-first-post-order
#
# See here for information about the differences between each algorithm.
# - https://en.wikipedia.org/wiki/Breadth-first_search
# - https://en.wikipedia.org/wiki/Depth-first_search
inheritance-traversal-algorithm: depth-first-pre-order
# Define special group weights for this server.
# Default is just 0.
group-weight:

View File

@@ -54,6 +54,7 @@ import me.lucko.luckperms.common.dependencies.DependencyRegistry;
import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.inheritance.InheritanceHandler;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
import me.lucko.luckperms.common.locale.SimpleLocaleManager;
@@ -112,6 +113,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
private LocaleManager localeManager;
private PluginClassLoader pluginClassLoader;
private DependencyManager dependencyManager;
private InheritanceHandler inheritanceHandler;
private CachedStateManager cachedStateManager;
private ContextManager<ProxiedPlayer> contextManager;
private CalculatorFactory calculatorFactory;
@@ -182,6 +184,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
// load internal managers
getLog().info("Loading internal permission managers...");
this.inheritanceHandler = new InheritanceHandler(this);
this.userManager = new StandardUserManager(this);
this.groupManager = new StandardGroupManager(this);
this.trackManager = new StandardTrackManager(this);
@@ -460,6 +463,11 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin {
return this.contextManager;
}
@Override
public InheritanceHandler getInheritanceHandler() {
return this.inheritanceHandler;
}
@Override
public CalculatorFactory getCalculatorFactory() {
return this.calculatorFactory;

View File

@@ -27,35 +27,35 @@ package me.lucko.luckperms.bungee.event;
import me.lucko.luckperms.api.Tristate;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* Copy of the internal BungeeCord PermissionCheckEvent, returning a tristate instead of a boolean.
*/
public class TristateCheckEvent extends Event {
public static Tristate call(ProxiedPlayer player, String permission) {
return ProxyServer.getInstance().getPluginManager().callEvent(new TristateCheckEvent(player, permission)).getResult();
public static Tristate call(CommandSender sender, String permission) {
return ProxyServer.getInstance().getPluginManager().callEvent(new TristateCheckEvent(sender, permission)).getResult();
}
private final ProxiedPlayer player;
private final CommandSender sender;
private final String permission;
private Tristate result;
public TristateCheckEvent(ProxiedPlayer player, String permission) {
this(player, permission, player.getPermissions().contains(permission) ? Tristate.TRUE : Tristate.UNDEFINED);
public TristateCheckEvent(CommandSender sender, String permission) {
this(sender, permission, sender.getPermissions().contains(permission) ? Tristate.TRUE : Tristate.UNDEFINED);
}
public TristateCheckEvent(ProxiedPlayer player, String permission, Tristate result) {
this.player = player;
public TristateCheckEvent(CommandSender sender, String permission, Tristate result) {
this.sender = sender;
this.permission = permission;
this.result = result;
}
public ProxiedPlayer getPlayer() {
return this.player;
public CommandSender getSender() {
return this.sender;
}
public String getPermission() {
@@ -73,7 +73,7 @@ public class TristateCheckEvent extends Event {
@Override
public String toString() {
return "TristateCheckEvent(" +
"player=" + this.player + ", " +
"sender=" + this.sender + ", " +
"permission=" + this.permission + ", " +
"result=" + this.result + ")";
}

View File

@@ -77,10 +77,14 @@ public class BungeePermissionCheckListener implements Listener {
@EventHandler
public void onPlayerTristateCheck(TristateCheckEvent e) {
ProxiedPlayer player = e.getPlayer();
if (!(e.getSender() instanceof ProxiedPlayer)) {
return;
}
Objects.requireNonNull(e.getPermission(), "permission");
Objects.requireNonNull(player, "player");
Objects.requireNonNull(e.getSender(), "sender");
ProxiedPlayer player = ((ProxiedPlayer) e.getSender());
User user = this.plugin.getUserManager().getIfLoaded(player.getUniqueId());
if (user == null) {

View File

@@ -131,6 +131,18 @@ apply-shorthand: true
# If set to false, LuckPerms will ignore these values.
apply-bungee-config-permissions: false
# The algorithm LuckPerms should use when traversing the "inheritance tree".
#
# The valid options are:
# - breadth-first
# - depth-first-pre-order
# - depth-first-post-order
#
# See here for information about the differences between each algorithm.
# - https://en.wikipedia.org/wiki/Breadth-first_search
# - https://en.wikipedia.org/wiki/Depth-first_search
inheritance-traversal-algorithm: depth-first-pre-order
# Define special group weights for this server.
# Default is just 0.
group-weight:

View File

@@ -117,9 +117,9 @@ public abstract class HolderCachedData<T extends PermissionHolder> implements Ca
}
if (contexts.getContexts() == Contexts.allowAll()) {
data.loadMeta(this.holder.accumulateMeta(newAccumulator(contexts), null));
data.loadMeta(this.holder.accumulateMeta(newAccumulator(contexts)));
} else {
data.loadMeta(this.holder.accumulateMeta(newAccumulator(contexts), null, contexts.getContexts()));
data.loadMeta(this.holder.accumulateMeta(newAccumulator(contexts), contexts.getContexts()));
}
return data;

View File

@@ -37,6 +37,7 @@ import me.lucko.luckperms.common.config.keys.IntegerKey;
import me.lucko.luckperms.common.config.keys.LowercaseStringKey;
import me.lucko.luckperms.common.config.keys.MapKey;
import me.lucko.luckperms.common.config.keys.StringKey;
import me.lucko.luckperms.common.inheritance.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.model.TemporaryModifier;
@@ -222,6 +223,21 @@ public class ConfigKeys {
*/
public static final ConfigKey<Boolean> APPLY_SPONGE_DEFAULT_SUBJECTS = EnduringKey.wrap(BooleanKey.of("apply-sponge-default-subjects", true));
/**
* The algorithm LuckPerms should use when traversing the "inheritance tree"
*/
public static final ConfigKey<TraversalAlgorithm> INHERITANCE_TRAVERSAL_ALGORITHM = AbstractKey.of(c -> {
String value = c.getString("inheritance-traversal-algorithm", "depth-first-pre-order");
switch (value.toLowerCase()) {
case "breadth-first":
return TraversalAlgorithm.BREADTH_FIRST;
case "depth-first-post-order":
return TraversalAlgorithm.DEPTH_FIRST_POST_ORDER;
default:
return TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER;
}
});
/**
* The configured group weightings
*/

View File

@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.primarygroup;
package me.lucko.luckperms.common.inheritance;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
@@ -35,19 +35,19 @@ import java.util.Comparator;
/**
* Determines the order of group inheritance in {@link PermissionHolder}.
*/
public class GroupInheritanceComparator implements Comparator<Group> {
private static final Comparator<Group> NULL_ORIGIN = new GroupInheritanceComparator(null);
public class InheritanceComparator implements Comparator<Group> {
private static final Comparator<Group> NULL_ORIGIN = new InheritanceComparator(null);
public static Comparator<Group> getFor(PermissionHolder origin) {
if (origin.getType().isUser()) {
return new GroupInheritanceComparator(((User) origin));
return new InheritanceComparator(((User) origin));
}
return NULL_ORIGIN;
}
private final User origin;
private GroupInheritanceComparator(User origin) {
private InheritanceComparator(User origin) {
this.origin = origin;
}

View File

@@ -0,0 +1,111 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.inheritance;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.common.inheritance.graph.Graph;
import me.lucko.luckperms.common.inheritance.graph.GraphTraversers;
import me.lucko.luckperms.common.inheritance.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* A {@link Graph} which represents an "inheritance tree".
*/
public interface InheritanceGraph extends Graph<PermissionHolder> {
/**
* Returns an iterable which will traverse this inheritance graph using the
* specified algorithm starting at the given node.
*
* @param algorithm the algorithm to use when traversing
* @param startNode the start node in the inheritance graph
* @return an iterable
*/
default Iterable<PermissionHolder> traverse(TraversalAlgorithm algorithm, PermissionHolder startNode) {
return GraphTraversers.traverseUsing(algorithm, this, startNode);
}
final class NonContextual implements InheritanceGraph {
private final LuckPermsPlugin plugin;
NonContextual(LuckPermsPlugin plugin) {
this.plugin = plugin;
}
@Override
public Iterable<? extends PermissionHolder> successors(PermissionHolder holder) {
Set<Group> successors = new TreeSet<>(holder.getInheritanceComparator());
List<Node> nodes = holder.getOwnGroupNodes();
for (Node n : nodes) {
Group g = this.plugin.getGroupManager().getIfLoaded(n.getGroupName());
if (g != null) {
successors.add(g);
}
}
return successors;
}
}
final class Contextual implements InheritanceGraph {
private final LuckPermsPlugin plugin;
/**
* The contexts to resolve inheritance in.
*/
private final Contexts context;
Contextual(LuckPermsPlugin plugin, Contexts context) {
this.plugin = plugin;
this.context = context;
}
@Override
public Iterable<? extends PermissionHolder> successors(PermissionHolder holder) {
Set<Group> successors = new TreeSet<>(holder.getInheritanceComparator());
List<Node> nodes = holder.getOwnGroupNodes(this.context.getContexts());
for (Node n : nodes) {
// effectively: if not (we're applying global groups or it's specific anyways)
if (!((this.context.isApplyGlobalGroups() || n.isServerSpecific()) && (this.context.isApplyGlobalWorldGroups() || n.isWorldSpecific()))) {
continue;
}
Group g = this.plugin.getGroupManager().getIfLoaded(n.getGroupName());
if (g != null) {
successors.add(g);
}
}
return successors;
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.inheritance;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.concurrent.TimeUnit;
/**
* Provides {@link InheritanceGraph}s.
*/
public class InheritanceHandler {
private final LuckPermsPlugin plugin;
/**
* An inheritance graph which doesn't consider contexts
*/
private final InheritanceGraph nonContextualGraph;
/**
* Cache of contextual inheritance graph instances
*/
private final LoadingCache<Contexts, InheritanceGraph> contextualGraphs;
public InheritanceHandler(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.nonContextualGraph = new InheritanceGraph.NonContextual(plugin);
this.contextualGraphs = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(key -> new InheritanceGraph.Contextual(this.plugin, key));
}
public InheritanceGraph getGraph() {
return this.nonContextualGraph;
}
public InheritanceGraph getGraph(Contexts contexts) {
return this.contextualGraphs.get(contexts);
}
}

View File

@@ -0,0 +1,44 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.inheritance.graph;
/**
* A minimal functional interface for graph-structured data.
*
* @param <N> the node parameter type
*/
@FunctionalInterface
public interface Graph<N> {
/**
* Returns all nodes in this graph directly adjacent to {@code node} which
* can be reached by traversing {@code node}'s outgoing edges.
*
* @throws IllegalArgumentException if {@code node} is not an element of this graph
*/
Iterable<? extends N> successors(N node);
}

View File

@@ -0,0 +1,215 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.
*/
/*
* Copyright (C) 2017 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.lucko.luckperms.common.inheritance.graph;
import com.google.common.collect.AbstractIterator;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
/**
* A collection of graph traversal algorithms.
*
* @author Jens Nyman
*/
public final class GraphTraversers {
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from
* {@code startNode}, in the order defined by the {@code algorithm}.
*/
public static <N> Iterable<N> traverseUsing(TraversalAlgorithm algorithm, Graph<N> graph, N startNode) {
Objects.requireNonNull(algorithm, "algorithm");
switch (algorithm) {
case BREADTH_FIRST:
return breadthFirst(graph, startNode);
case DEPTH_FIRST_PRE_ORDER:
return depthFirstPreOrder(graph, startNode);
case DEPTH_FIRST_POST_ORDER:
return depthFirstPostOrder(graph, startNode);
default:
throw new AssertionError();
}
}
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from
* {@code startNode}, in the order of a breadth-first traversal. That is,
* all the nodes of depth 0 are returned, then depth 1, then 2, and so on.
*
* <p>See <a href="https://en.wikipedia.org/wiki/Breadth-first_search">Wikipedia</a> for more info.</p>
*/
public static <N> Iterable<N> breadthFirst(Graph<N> graph, N startNode) {
Objects.requireNonNull(graph, "graph");
Objects.requireNonNull(startNode, "startNode");
return () -> new BreadthFirstIterator<>(graph, startNode);
}
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from
* {@code startNode}, in the order of a depth-first pre-order traversal.
* "Pre-order" implies that nodes appear in the {@code Iterable} in the
* order in which they are first visited.
*
* <p>See <a href="https://en.wikipedia.org/wiki/Depth-first_search">Wikipedia</a> for more info.</p>
*/
public static <N> Iterable<N> depthFirstPreOrder(Graph<N> graph, N startNode) {
Objects.requireNonNull(graph, "graph");
Objects.requireNonNull(startNode, "startNode");
return () -> new DepthFirstIterator<>(graph, startNode, Order.PRE_ORDER);
}
/**
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
* the order of a depth-first post-order traversal. "Post-order" implies that nodes appear in the
* {@code Iterable} in the order in which they are visited for the last time.
*
* <p>See <a href="https://en.wikipedia.org/wiki/Depth-first_search">Wikipedia</a> for more info.</p>
*/
public static <N> Iterable<N> depthFirstPostOrder(Graph<N> graph, N startNode) {
Objects.requireNonNull(graph, "graph");
Objects.requireNonNull(startNode, "startNode");
return () -> new DepthFirstIterator<>(graph, startNode, Order.POST_ORDER);
}
private static final class BreadthFirstIterator<N> implements Iterator<N> {
private final Graph<N> graph;
private final Queue<N> queue = new ArrayDeque<>();
private final Set<N> visited = new HashSet<>();
BreadthFirstIterator(Graph<N> graph, N root) {
this.graph = graph;
this.queue.add(root);
this.visited.add(root);
}
@Override
public boolean hasNext() {
return !this.queue.isEmpty();
}
@Override
public N next() {
N current = this.queue.remove();
for (N neighbor : this.graph.successors(current)) {
if (this.visited.add(neighbor)) {
this.queue.add(neighbor);
}
}
return current;
}
}
private static final class DepthFirstIterator<N> extends AbstractIterator<N> {
private final Graph<N> graph;
private final Deque<NodeAndSuccessors> stack = new ArrayDeque<>();
private final Set<N> visited = new HashSet<>();
private final Order order;
DepthFirstIterator(Graph<N> graph, N root, Order order) {
this.graph = graph;
// our invariant is that in computeNext we call next on the iterator at the top first, so we
// need to start with one additional item on that iterator
this.stack.push(withSuccessors(root));
this.order = order;
}
@Override
protected N computeNext() {
while (true) {
if (this.stack.isEmpty()) {
return endOfData();
}
NodeAndSuccessors node = this.stack.getFirst();
boolean firstVisit = this.visited.add(node.node);
boolean lastVisit = !node.successorIterator.hasNext();
boolean produceNode = (firstVisit && this.order == Order.PRE_ORDER) || (lastVisit && this.order == Order.POST_ORDER);
if (lastVisit) {
this.stack.pop();
} else {
// we need to push a neighbor, but only if we haven't already seen it
N successor = node.successorIterator.next();
if (!this.visited.contains(successor)) {
this.stack.push(withSuccessors(successor));
}
}
if (produceNode) {
return node.node;
}
}
}
NodeAndSuccessors withSuccessors(N node) {
return new NodeAndSuccessors(node, this.graph.successors(node));
}
/**
* A simple tuple of a node and a partially iterated {@link Iterator} of
* its successors
*/
private final class NodeAndSuccessors {
final N node;
final Iterator<? extends N> successorIterator;
NodeAndSuccessors(N node, Iterable<? extends N> successors) {
this.node = node;
this.successorIterator = successors.iterator();
}
}
}
private enum Order {
PRE_ORDER,
POST_ORDER
}
private GraphTraversers() {}
}

View File

@@ -0,0 +1,59 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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.inheritance.graph;
public enum TraversalAlgorithm {
/**
* Traverses in breadth-first order.
*
* <p>That is, all the nodes of depth 0 are returned, then depth 1, then 2, and so on.</p>
*
* <p>See <a href="https://en.wikipedia.org/wiki/Breadth-first_search">Wikipedia</a> for more info.</p>
*/
BREADTH_FIRST,
/**
* Traverses in depth-first pre-order.
*
* <p>"Pre-order" implies that nodes appear in the order in which they are
* first visited.</p>
*
* <p>See <a href="https://en.wikipedia.org/wiki/Depth-first_search">Wikipedia</a> for more info.</p>
*/
DEPTH_FIRST_PRE_ORDER,
/**
* Traverses in depth-first post-order.
*
* <p>"Post-order" implies that nodes appear in the order in which they are
* visited for the last time.</p>
*
* <p>See <a href="https://en.wikipedia.org/wiki/Depth-first_search">Wikipedia</a> for more info.</p>
*/
DEPTH_FIRST_POST_ORDER
}

View File

@@ -83,6 +83,15 @@ public final class NodeMap {
.treeSetValues(NodeComparator.reverse())
.build();
/**
* Copy of {@link #map} which only contains group nodes
* @see Node#isGroupNode()
*/
private final SortedSetMultimap<ImmutableContextSet, Node> inheritanceMap = MultimapBuilder
.treeKeys(ContextSetComparator.reverse())
.treeSetValues(NodeComparator.reverse())
.build();
/**
* The lock which synchronizes the instance
*/
@@ -143,6 +152,28 @@ public final class NodeMap {
}
}
public void copyGroupNodesTo(Collection<? super Node> collection) {
this.lock.lock();
try {
collection.addAll(this.inheritanceMap.values());
} finally {
this.lock.unlock();
}
}
public void copyGroupNodesTo(Collection<? super Node> collection, ContextSet filter) {
this.lock.lock();
try {
for (Map.Entry<ImmutableContextSet, Collection<Node>> e : this.inheritanceMap.asMap().entrySet()) {
if (e.getKey().isSatisfiedBy(filter)) {
collection.addAll(e.getValue());
}
}
} finally {
this.lock.unlock();
}
}
public void copyToLocalized(Collection<LocalizedNode> collection) {
this.lock.lock();
try {
@@ -173,17 +204,11 @@ public final class NodeMap {
void add(Node node) {
this.lock.lock();
try {
this.map.put(node.getFullContexts().makeImmutable(), node);
} finally {
this.lock.unlock();
}
}
void replace(Node node, Node previous) {
this.lock.lock();
try {
this.map.remove(previous.getFullContexts().makeImmutable(), previous);
this.map.put(node.getFullContexts().makeImmutable(), node);
ImmutableContextSet context = node.getFullContexts().makeImmutable();
this.map.put(context, node);
if (node.isGroupNode() && node.getValuePrimitive()) {
this.inheritanceMap.put(context, node);
}
} finally {
this.lock.unlock();
}
@@ -192,7 +217,34 @@ public final class NodeMap {
void remove(Node node) {
this.lock.lock();
try {
this.map.get(node.getFullContexts().makeImmutable()).removeIf(e -> e.almostEquals(node));
ImmutableContextSet context = node.getFullContexts().makeImmutable();
this.map.get(context).removeIf(e -> e.almostEquals(node));
if (node.isGroupNode()) {
this.inheritanceMap.get(context).removeIf(e -> e.almostEquals(node));
}
} finally {
this.lock.unlock();
}
}
private void removeExact(Node node) {
this.lock.lock();
try {
ImmutableContextSet context = node.getFullContexts().makeImmutable();
this.map.remove(context, node);
if (node.isGroupNode() && node.getValuePrimitive()) {
this.inheritanceMap.remove(context, node);
}
} finally {
this.lock.unlock();
}
}
void replace(Node node, Node previous) {
this.lock.lock();
try {
removeExact(previous);
add(node);
} finally {
this.lock.unlock();
}
@@ -202,6 +254,7 @@ public final class NodeMap {
this.lock.lock();
try {
this.map.clear();
this.inheritanceMap.clear();
} finally {
this.lock.unlock();
}
@@ -210,7 +263,9 @@ public final class NodeMap {
void clear(ContextSet contextSet) {
this.lock.lock();
try {
this.map.removeAll(contextSet.makeImmutable());
ImmutableContextSet context = contextSet.makeImmutable();
this.map.removeAll(context);
this.inheritanceMap.removeAll(context);
} finally {
this.lock.unlock();
}
@@ -220,8 +275,9 @@ public final class NodeMap {
this.lock.lock();
try {
this.map.clear();
this.inheritanceMap.clear();
for (Node n : set) {
this.map.put(n.getFullContexts().makeImmutable(), n);
add(n);
}
} finally {
this.lock.unlock();
@@ -232,7 +288,14 @@ public final class NodeMap {
this.lock.lock();
try {
this.map.clear();
this.inheritanceMap.clear();
this.map.putAll(multimap);
for (Map.Entry<ImmutableContextSet, Node> entry : this.map.entries()) {
if (entry.getValue().isGroupNode() && entry.getValue().getValuePrimitive()) {
this.inheritanceMap.put(entry.getKey(), entry.getValue());
}
}
} finally {
this.lock.unlock();
}
@@ -241,7 +304,11 @@ public final class NodeMap {
boolean removeIf(Predicate<? super Node> predicate) {
this.lock.lock();
try {
return this.map.values().removeIf(predicate);
boolean ret = this.map.values().removeIf(predicate);
if (ret) {
this.inheritanceMap.values().removeIf(predicate);
}
return ret;
} finally {
this.lock.unlock();
}
@@ -250,8 +317,13 @@ public final class NodeMap {
boolean removeIf(ContextSet contextSet, Predicate<? super Node> predicate) {
this.lock.lock();
try {
SortedSet<Node> nodes = this.map.get(contextSet.makeImmutable());
return nodes != null && nodes.removeIf(predicate);
ImmutableContextSet context = contextSet.makeImmutable();
SortedSet<Node> nodes = this.map.get(context);
boolean ret = nodes.removeIf(predicate);
if (ret) {
this.inheritanceMap.get(context).removeIf(predicate);
}
return ret;
} finally {
this.lock.unlock();
}
@@ -269,6 +341,9 @@ public final class NodeMap {
if (removed != null) {
removed.add(entry);
}
if (entry.isGroupNode() && entry.getValuePrimitive()) {
this.inheritanceMap.remove(entry.getFullContexts().makeImmutable(), entry);
}
work = true;
it.remove();
}

View File

@@ -44,6 +44,8 @@ import me.lucko.luckperms.common.caching.HolderCachedData;
import me.lucko.luckperms.common.caching.handlers.StateListener;
import me.lucko.luckperms.common.caching.type.MetaAccumulator;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.inheritance.InheritanceComparator;
import me.lucko.luckperms.common.inheritance.InheritanceGraph;
import me.lucko.luckperms.common.node.ImmutableLocalizedNode;
import me.lucko.luckperms.common.node.InheritanceInfo;
import me.lucko.luckperms.common.node.MetaType;
@@ -51,7 +53,6 @@ import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.node.NodeTools;
import me.lucko.luckperms.common.node.NodeWithContextComparator;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.primarygroup.GroupInheritanceComparator;
import me.lucko.luckperms.common.references.GroupReference;
import me.lucko.luckperms.common.references.HolderReference;
import me.lucko.luckperms.common.references.HolderType;
@@ -151,7 +152,7 @@ public abstract class PermissionHolder {
/**
* Comparator used to ordering groups when calculating inheritance
*/
private final Comparator<Group> inheritanceComparator = GroupInheritanceComparator.getFor(this);
private final Comparator<Group> inheritanceComparator = InheritanceComparator.getFor(this);
/**
* A set of runnables which are called when this objects state changes.
@@ -235,6 +236,10 @@ public abstract class PermissionHolder {
*/
public abstract HolderType getType();
public Comparator<Group> getInheritanceComparator() {
return this.inheritanceComparator;
}
public NodeMap getData(NodeMapType type) {
switch (type) {
case ENDURING:
@@ -296,6 +301,20 @@ public abstract class PermissionHolder {
return ret;
}
public List<Node> getOwnGroupNodes() {
List<Node> ret = new ArrayList<>();
this.transientNodes.copyGroupNodesTo(ret);
this.enduringNodes.copyGroupNodesTo(ret);
return ret;
}
public List<Node> getOwnGroupNodes(ContextSet filter) {
List<Node> ret = new ArrayList<>();
this.transientNodes.copyGroupNodesTo(ret, filter);
this.enduringNodes.copyGroupNodesTo(ret, filter);
return ret;
}
public SortedSet<LocalizedNode> getOwnNodesSorted() {
SortedSet<LocalizedNode> ret = new TreeSet<>(NodeWithContextComparator.reverse());
this.transientNodes.copyToLocalized(ret);
@@ -323,69 +342,28 @@ public abstract class PermissionHolder {
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
*/
public List<LocalizedNode> resolveInheritances(List<LocalizedNode> accumulator, Set<String> excludedGroups, Contexts context) {
if (accumulator == null) {
accumulator = new ArrayList<>();
}
if (excludedGroups == null) {
excludedGroups = new HashSet<>();
}
if (this.getType().isGroup()) {
excludedGroups.add(getObjectName().toLowerCase());
}
// get and add the objects own nodes
List<Node> nodes = getOwnNodes(context.getContexts());
for (Node node : nodes) {
ImmutableLocalizedNode localizedNode = ImmutableLocalizedNode.of(node, getObjectName());
accumulator.add(localizedNode);
}
// resolve and process the objects parents
List<Group> resolvedGroups = new ArrayList<>();
Set<String> processedGroups = new HashSet<>();
for (Node n : nodes) {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
if (!((context.isApplyGlobalGroups() || n.isServerSpecific()) && (context.isApplyGlobalWorldGroups() || n.isWorldSpecific()))) {
continue;
}
Group g = this.plugin.getGroupManager().getIfLoaded(groupName);
if (g != null) {
resolvedGroups.add(g);
private void accumulateInheritancesTo(List<LocalizedNode> accumulator, Contexts context) {
InheritanceGraph graph = this.plugin.getInheritanceHandler().getGraph(context);
Iterable<PermissionHolder> traversal = graph.traverse(this.plugin.getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), this);
for (PermissionHolder holder : traversal) {
List<Node> nodes = holder.getOwnNodes(context.getContexts());
for (Node node : nodes) {
ImmutableLocalizedNode localizedNode = ImmutableLocalizedNode.of(node, holder.getObjectName());
accumulator.add(localizedNode);
}
}
// sort the groups according to weight + other factors.
resolvedGroups.sort(this.inheritanceComparator);
for (Group g : resolvedGroups) {
g.resolveInheritances(accumulator, excludedGroups, context);
}
return accumulator;
}
public List<LocalizedNode> resolveInheritances(Contexts context) {
return resolveInheritances(null, null, context);
List<LocalizedNode> accumulator = new ArrayList<>();
accumulateInheritancesTo(accumulator, context);
return accumulator;
}
public SortedSet<LocalizedNode> resolveInheritancesAlmostEqual(Contexts contexts) {
List<LocalizedNode> nodes = resolveInheritances(new LinkedList<>(), null, contexts);
List<LocalizedNode> nodes = new LinkedList<>();
accumulateInheritancesTo(nodes, contexts);
NodeTools.removeAlmostEqual(nodes.iterator());
SortedSet<LocalizedNode> ret = new TreeSet<>(NodeWithContextComparator.reverse());
ret.addAll(nodes);
@@ -393,71 +371,37 @@ public abstract class PermissionHolder {
}
public SortedSet<LocalizedNode> resolveInheritancesMergeTemp(Contexts contexts) {
List<LocalizedNode> nodes = resolveInheritances(new LinkedList<>(), null, contexts);
List<LocalizedNode> nodes = new LinkedList<>();
accumulateInheritancesTo(nodes, contexts);
NodeTools.removeIgnoreValueOrTemp(nodes.iterator());
SortedSet<LocalizedNode> ret = new TreeSet<>(NodeWithContextComparator.reverse());
ret.addAll(nodes);
return ret;
}
/**
* Resolves inherited nodes and returns them
*
* @param excludedGroups a list of groups to exclude
* @return a set of nodes
*/
public List<LocalizedNode> resolveInheritances(List<LocalizedNode> accumulator, Set<String> excludedGroups) {
if (accumulator == null) {
accumulator = new ArrayList<>();
}
if (excludedGroups == null) {
excludedGroups = new HashSet<>();
}
if (this.getType().isGroup()) {
excludedGroups.add(getObjectName().toLowerCase());
}
// get and add the objects own nodes
List<Node> nodes = getOwnNodes();
for (Node node : nodes) {
ImmutableLocalizedNode localizedNode = ImmutableLocalizedNode.of(node, getObjectName());
accumulator.add(localizedNode);
}
// resolve and process the objects parents
List<Group> resolvedGroups = new ArrayList<>();
Set<String> processedGroups = new HashSet<>();
for (Node n : nodes) {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
Group g = this.plugin.getGroupManager().getIfLoaded(groupName);
if (g != null) {
resolvedGroups.add(g);
private void accumulateInheritancesTo(List<LocalizedNode> accumulator) {
InheritanceGraph graph = this.plugin.getInheritanceHandler().getGraph();
Iterable<PermissionHolder> traversal = graph.traverse(this.plugin.getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), this);
for (PermissionHolder holder : traversal) {
List<Node> nodes = holder.getOwnNodes();
for (Node node : nodes) {
ImmutableLocalizedNode localizedNode = ImmutableLocalizedNode.of(node, holder.getObjectName());
accumulator.add(localizedNode);
}
}
// sort the groups according to weight + other factors.
resolvedGroups.sort(this.inheritanceComparator);
for (Group g : resolvedGroups) {
g.resolveInheritances(accumulator, excludedGroups);
}
return accumulator;
}
public List<LocalizedNode> resolveInheritances() {
return resolveInheritances(null, null);
List<LocalizedNode> accumulator = new ArrayList<>();
accumulateInheritancesTo(accumulator);
return accumulator;
}
public SortedSet<LocalizedNode> resolveInheritancesAlmostEqual() {
List<LocalizedNode> nodes = resolveInheritances(new LinkedList<>(), null);
List<LocalizedNode> nodes = new LinkedList<>();
accumulateInheritancesTo(nodes);
NodeTools.removeAlmostEqual(nodes.iterator());
SortedSet<LocalizedNode> ret = new TreeSet<>(NodeWithContextComparator.reverse());
ret.addAll(nodes);
@@ -465,19 +409,20 @@ public abstract class PermissionHolder {
}
public SortedSet<LocalizedNode> resolveInheritancesMergeTemp() {
List<LocalizedNode> nodes = resolveInheritances(new LinkedList<>(), null);
List<LocalizedNode> nodes = new LinkedList<>();
accumulateInheritancesTo(nodes);
NodeTools.removeIgnoreValueOrTemp(nodes.iterator());
SortedSet<LocalizedNode> ret = new TreeSet<>(NodeWithContextComparator.reverse());
ret.addAll(nodes);
return ret;
}
public SortedSet<LocalizedNode> getAllNodes(Contexts context) {
List<LocalizedNode> entries;
private List<LocalizedNode> getAllEntries(Contexts context) {
List<LocalizedNode> entries = new LinkedList<>();
if (context.isApplyGroups()) {
entries = resolveInheritances(new LinkedList<>(), null, context);
accumulateInheritancesTo(entries, context);
} else {
entries = new LinkedList<>();
for (Node n : getOwnNodes(context.getContexts())) {
ImmutableLocalizedNode localizedNode = ImmutableLocalizedNode.of(n, getObjectName());
entries.add(localizedNode);
@@ -491,6 +436,12 @@ public abstract class PermissionHolder {
entries.removeIf(n -> !n.isGroupNode() && !n.isWorldSpecific());
}
return entries;
}
public SortedSet<LocalizedNode> getAllNodes(Contexts context) {
List<LocalizedNode> entries = getAllEntries(context);
NodeTools.removeSamePermission(entries.iterator());
SortedSet<LocalizedNode> ret = new TreeSet<>(NodeWithContextComparator.reverse());
ret.addAll(entries);
@@ -498,23 +449,7 @@ public abstract class PermissionHolder {
}
public Map<String, Boolean> exportNodesAndShorthand(Contexts context, boolean lowerCase) {
List<LocalizedNode> entries;
if (context.isApplyGroups()) {
entries = resolveInheritances(new LinkedList<>(), null, context);
} else {
entries = new LinkedList<>();
for (Node n : getOwnNodes(context.getContexts())) {
ImmutableLocalizedNode localizedNode = ImmutableLocalizedNode.of(n, getObjectName());
entries.add(localizedNode);
}
}
if (!context.isIncludeGlobal()) {
entries.removeIf(n -> !n.isGroupNode() && !n.isServerSpecific());
}
if (!context.isApplyGlobalWorldGroups()) {
entries.removeIf(n -> !n.isGroupNode() && !n.isWorldSpecific());
}
List<LocalizedNode> entries = getAllEntries(context);
Map<String, Boolean> perms = new HashMap<>();
boolean applyShorthand = this.plugin.getConfiguration().get(ConfigKeys.APPLYING_SHORTHAND);
@@ -557,117 +492,55 @@ public abstract class PermissionHolder {
return ImmutableMap.copyOf(perms);
}
public MetaAccumulator accumulateMeta(MetaAccumulator accumulator, Set<String> excludedGroups, Contexts context) {
public MetaAccumulator accumulateMeta(MetaAccumulator accumulator, Contexts context) {
if (accumulator == null) {
accumulator = MetaAccumulator.makeFromConfig(this.plugin);
}
if (excludedGroups == null) {
excludedGroups = new HashSet<>();
}
InheritanceGraph graph = this.plugin.getInheritanceHandler().getGraph(context);
Iterable<PermissionHolder> traversal = graph.traverse(this.plugin.getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), this);
for (PermissionHolder holder : traversal) {
List<Node> nodes = holder.getOwnNodes(context.getContexts());
for (Node node : nodes) {
if (!node.getValuePrimitive()) continue;
if (!node.isMeta() && !node.isPrefix() && !node.isSuffix()) continue;
if (this.getType().isGroup()) {
excludedGroups.add(getObjectName().toLowerCase());
}
if (!((context.isIncludeGlobal() || node.isServerSpecific()) && (context.isIncludeGlobalWorld() || node.isWorldSpecific()))) {
continue;
}
// get and add the objects own nodes
List<Node> nodes = getOwnNodes(context.getContexts());
for (Node node : nodes) {
if (!node.getValuePrimitive()) continue;
if (!node.isMeta() && !node.isPrefix() && !node.isSuffix()) continue;
if (!((context.isIncludeGlobal() || node.isServerSpecific()) && (context.isIncludeGlobalWorld() || node.isWorldSpecific()))) {
continue;
accumulator.accumulateNode(ImmutableLocalizedNode.of(node, holder.getObjectName()));
}
accumulator.accumulateNode(ImmutableLocalizedNode.of(node, getObjectName()));
}
OptionalInt w = getWeight();
if (w.isPresent()) {
accumulator.accumulateWeight(w.getAsInt());
}
// resolve and process the objects parents
List<Group> resolvedGroups = new ArrayList<>();
Set<String> processedGroups = new HashSet<>();
for (Node n : nodes) {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
if (!((context.isApplyGlobalGroups() || n.isServerSpecific()) && (context.isApplyGlobalWorldGroups() || n.isWorldSpecific()))) {
continue;
OptionalInt w = holder.getWeight();
if (w.isPresent()) {
accumulator.accumulateWeight(w.getAsInt());
}
Group g = this.plugin.getGroupManager().getIfLoaded(groupName);
if (g != null) {
resolvedGroups.add(g);
}
}
// sort the groups according to weight + other factors.
resolvedGroups.sort(this.inheritanceComparator);
for (Group g : resolvedGroups) {
g.accumulateMeta(accumulator, excludedGroups, context);
}
return accumulator;
}
public MetaAccumulator accumulateMeta(MetaAccumulator accumulator, Set<String> excludedGroups) {
public MetaAccumulator accumulateMeta(MetaAccumulator accumulator) {
if (accumulator == null) {
accumulator = MetaAccumulator.makeFromConfig(this.plugin);
}
if (excludedGroups == null) {
excludedGroups = new HashSet<>();
}
InheritanceGraph graph = this.plugin.getInheritanceHandler().getGraph();
Iterable<PermissionHolder> traversal = graph.traverse(this.plugin.getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), this);
for (PermissionHolder holder : traversal) {
List<Node> nodes = holder.getOwnNodes();
for (Node node : nodes) {
if (!node.getValuePrimitive()) continue;
if (!node.isMeta() && !node.isPrefix() && !node.isSuffix()) continue;
if (this.getType().isGroup()) {
excludedGroups.add(getObjectName().toLowerCase());
}
// get and add the objects own nodes
List<Node> nodes = getOwnNodes();
for (Node node : nodes) {
if (!node.getValuePrimitive()) continue;
if (!node.isMeta() && !node.isPrefix() && !node.isSuffix()) continue;
accumulator.accumulateNode(ImmutableLocalizedNode.of(node, getObjectName()));
}
OptionalInt w = getWeight();
if (w.isPresent()) {
accumulator.accumulateWeight(w.getAsInt());
}
// resolve and process the objects parents
List<Group> resolvedGroups = new ArrayList<>();
Set<String> processedGroups = new HashSet<>();
for (Node n : nodes) {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
Group g = this.plugin.getGroupManager().getIfLoaded(groupName);
if (g != null) {
resolvedGroups.add(g);
accumulator.accumulateNode(ImmutableLocalizedNode.of(node, holder.getObjectName()));
}
}
// sort the groups according to weight + other factors.
resolvedGroups.sort(this.inheritanceComparator);
for (Group g : resolvedGroups) {
g.accumulateMeta(accumulator, excludedGroups);
OptionalInt w = getWeight();
if (w.isPresent()) {
accumulator.accumulateWeight(w.getAsInt());
}
}
return accumulator;
@@ -1019,8 +892,7 @@ public abstract class PermissionHolder {
}
public Set<HolderReference> getGroupReferences() {
return getOwnNodes().stream()
.filter(Node::isGroupNode)
return getOwnGroupNodes().stream()
.map(Node::getGroupName)
.map(String::toLowerCase)
.map(GroupReference::of)

View File

@@ -41,6 +41,7 @@ import me.lucko.luckperms.common.contexts.ContextManager;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.inheritance.InheritanceHandler;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.logging.Logger;
import me.lucko.luckperms.common.managers.group.GroupManager;
@@ -178,6 +179,13 @@ public interface LuckPermsPlugin {
*/
ContextManager<?> getContextManager();
/**
* Gets the inheritance handler
*
* @return the inheritance handler
*/
InheritanceHandler getInheritanceHandler();
/**
* Gets the cached state manager for the platform.
*

View File

@@ -25,15 +25,16 @@
package me.lucko.luckperms.common.primarygroup;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.inheritance.InheritanceGraph;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.User;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class AllParentsByWeightHolder extends CachedPrimaryGroupHolder {
public AllParentsByWeightHolder(User user) {
@@ -47,23 +48,21 @@ public class AllParentsByWeightHolder extends CachedPrimaryGroupHolder {
contexts = this.user.getPlugin().getContextManager().getStaticContexts();
}
// hack to get a list of groups the holder is inheriting from
Set<String> groupNames = new LinkedHashSet<>();
this.user.resolveInheritances(new NoopList<>(), groupNames, contexts);
InheritanceGraph graph = this.user.getPlugin().getInheritanceHandler().getGraph(contexts);
List<Group> groups = new ArrayList<>();
for (String groupName : groupNames) {
Group group = this.user.getPlugin().getGroupManager().getIfLoaded(groupName);
if (group != null) {
groups.add(group);
}
}
// fully traverse the graph, obtain a list of permission holders the user inherits from
List<PermissionHolder> traversal = ImmutableList.copyOf(graph.traverse(this.user.getPlugin().getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), this.user));
Group bestGroup = null;
if (!groups.isEmpty()) {
if (!traversal.isEmpty()) {
int best = 0;
for (Group g : groups) {
for (PermissionHolder holder : traversal) {
if (!(holder instanceof Group)) {
continue;
}
Group g = ((Group) holder);
int weight = g.getWeight().orElse(0);
if (bestGroup == null || g.getWeight().orElse(0) > best) {
bestGroup = g;
@@ -74,22 +73,4 @@ public class AllParentsByWeightHolder extends CachedPrimaryGroupHolder {
return bestGroup == null ? null : bestGroup.getName();
}
private static final class NoopList<E> extends AbstractList<E> implements List<E> {
@Override
public boolean add(E e) {
return true;
}
@Override
public E get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
}
}

View File

@@ -51,6 +51,7 @@ import me.lucko.luckperms.common.dependencies.DependencyRegistry;
import me.lucko.luckperms.common.dependencies.classloader.PluginClassLoader;
import me.lucko.luckperms.common.dependencies.classloader.ReflectionClassLoader;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.inheritance.InheritanceHandler;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.NoopLocaleManager;
import me.lucko.luckperms.common.locale.SimpleLocaleManager;
@@ -179,6 +180,7 @@ public class LPSpongePlugin implements LuckPermsSpongePlugin {
private LocaleManager localeManager;
private PluginClassLoader pluginClassLoader;
private DependencyManager dependencyManager;
private InheritanceHandler inheritanceHandler;
private CachedStateManager cachedStateManager;
private ContextManager<Subject> contextManager;
private CalculatorFactory calculatorFactory;
@@ -242,6 +244,7 @@ public class LPSpongePlugin implements LuckPermsSpongePlugin {
// load internal managers
getLog().info("Loading internal permission managers...");
this.inheritanceHandler = new InheritanceHandler(this);
this.userManager = new SpongeUserManager(this);
this.groupManager = new SpongeGroupManager(this);
this.trackManager = new StandardTrackManager(this);
@@ -609,6 +612,11 @@ public class LPSpongePlugin implements LuckPermsSpongePlugin {
return this.contextManager;
}
@Override
public InheritanceHandler getInheritanceHandler() {
return this.inheritanceHandler;
}
@Override
public CalculatorFactory getCalculatorFactory() {
return this.calculatorFactory;

View File

@@ -376,7 +376,7 @@ public class LuckPermsSubjectData implements LPSubjectData {
toRemove.forEach(makeUnsetConsumer(this.enduring));
MetaAccumulator metaAccumulator = this.holder.accumulateMeta(null, null, this.service.getPlugin().getContextManager().formContexts(contexts));
MetaAccumulator metaAccumulator = this.holder.accumulateMeta(null, this.service.getPlugin().getContextManager().formContexts(contexts));
int priority = metaAccumulator.getChatMeta(type).keySet().stream().mapToInt(e -> e).max().orElse(0);
priority += 10;

View File

@@ -128,6 +128,18 @@ apply-sponge-implicit-wildcards=true
# false, LuckPerms will ignore this data when considering if a player has a permission.
apply-sponge-default-subjects=true
# The algorithm LuckPerms should use when traversing the "inheritance tree".
#
# The valid options are:
# - breadth-first
# - depth-first-pre-order
# - depth-first-post-order
#
# See here for information about the differences between each algorithm.
# - https://en.wikipedia.org/wiki/Breadth-first_search
# - https://en.wikipedia.org/wiki/Depth-first_search
inheritance-traversal-algorithm="depth-first-pre-order"
# Define special group weights for this server.
# Default is just 0.
group-weight {