diff --git a/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java b/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java index 632c8196..59519229 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultChatHook.java @@ -29,13 +29,15 @@ import me.lucko.luckperms.api.vault.cache.VaultUserCache; import me.lucko.luckperms.contexts.Contexts; import me.lucko.luckperms.core.PermissionHolder; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; +import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.groups.Group; import me.lucko.luckperms.users.User; import net.milkbowl.vault.chat.Chat; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static me.lucko.luckperms.utils.ArgumentChecker.escapeCharacters; import static me.lucko.luckperms.utils.ArgumentChecker.unescapeCharacters; @@ -75,51 +77,58 @@ public class VaultChatHook extends Chat { private void saveMeta(PermissionHolder holder, String world, String node, String value) { if (holder == null) return; if (node.equals("")) return; - node = escapeCharacters(node); - value = escapeCharacters(value); - Iterator nodes = holder.getNodes().iterator(); - while (nodes.hasNext()) { - Node n = nodes.next(); - if (n.isMeta() && n.getMeta().getKey().equals(node)) { - nodes.remove(); + perms.scheduleTask(() -> { + String k = escapeCharacters(node); + String v = escapeCharacters(value); + + List toRemove = holder.getNodes().stream() + .filter(n -> n.isMeta() && n.getMeta().getKey().equals(k)) + .collect(Collectors.toList()); + + toRemove.forEach(n -> { + try { + holder.unsetPermission(n); + } catch (ObjectLacksException ignored) {} + }); + + Node.Builder metaNode = new me.lucko.luckperms.core.Node.Builder("meta." + k + "." + v).setValue(true); + if (!perms.getServer().equalsIgnoreCase("global")) { + metaNode.setServer(perms.getServer()); + } + if (world != null && !world.equals("")) { + metaNode.setServer(perms.getServer()).setWorld(world); } - } - Node.Builder metaNode = new me.lucko.luckperms.core.Node.Builder("meta." + node + "." + value).setValue(true); - if (!perms.getServer().equalsIgnoreCase("global")) { - metaNode.setServer(perms.getServer()); - } - if (world != null && !world.equals("")) { - metaNode.setServer(perms.getServer()).setWorld(world); - } + try { + holder.setPermission(metaNode.build()); + } catch (ObjectAlreadyHasException ignored) {} - try { - holder.setPermission(metaNode.build()); - } catch (ObjectAlreadyHasException ignored) {} - - perms.objectSave(holder); + perms.save(holder); + }); } private void setChatMeta(boolean prefix, PermissionHolder holder, String value, String world) { if (holder == null) return; if (value.equals("")) return; - Node.Builder node = new me.lucko.luckperms.core.Node.Builder(prefix ? "prefix" : "suffix" + ".1000." + escapeCharacters(value)); - node.setValue(true); - if (!perms.getServer().equalsIgnoreCase("global")) { - node.setServer(perms.getServer()); - } + perms.scheduleTask(() -> { + Node.Builder node = new me.lucko.luckperms.core.Node.Builder(prefix ? "prefix" : "suffix" + ".1000." + escapeCharacters(value)); + node.setValue(true); + if (!perms.getServer().equalsIgnoreCase("global")) { + node.setServer(perms.getServer()); + } - if (world != null && !world.equals("")) { - node.setServer(perms.getServer()).setWorld(world); - } + if (world != null && !world.equals("")) { + node.setServer(perms.getServer()).setWorld(world); + } - try { - holder.setPermission(node.build()); - } catch (ObjectAlreadyHasException ignored) {} + try { + holder.setPermission(node.build()); + } catch (ObjectAlreadyHasException ignored) {} - perms.objectSave(holder); + perms.save(holder); + }); } private String getUserMeta(User user, String world, String node, String defaultValue) { diff --git a/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultPermissionHook.java b/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultPermissionHook.java index e45faabf..da4225de 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultPermissionHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/api/vault/VaultPermissionHook.java @@ -27,7 +27,6 @@ import lombok.NonNull; import lombok.Setter; import me.lucko.luckperms.LPBukkitPlugin; import me.lucko.luckperms.api.Node; -import me.lucko.luckperms.api.data.Callback; import me.lucko.luckperms.api.vault.cache.VaultUserCache; import me.lucko.luckperms.api.vault.cache.VaultUserManager; import me.lucko.luckperms.contexts.Contexts; @@ -38,11 +37,10 @@ import me.lucko.luckperms.groups.Group; import me.lucko.luckperms.users.User; import net.milkbowl.vault.permission.Permission; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; -public class VaultPermissionHook extends Permission { +public class VaultPermissionHook extends Permission implements Runnable { + private final List tasks = new ArrayList<>(); @Getter @Setter @@ -76,6 +74,7 @@ public class VaultPermissionHook extends Permission { public void setup() { vaultUserManager = new VaultUserManager(plugin, this); + plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, this, 1L, 1L); } public void log(String s) { @@ -84,6 +83,23 @@ public class VaultPermissionHook extends Permission { } } + void scheduleTask(Runnable r) { + synchronized (tasks) { + tasks.add(r); + } + } + + @Override + public void run() { + List toRun = new ArrayList<>(); + synchronized (tasks) { + toRun.addAll(tasks); + tasks.clear(); + } + + toRun.forEach(Runnable::run); + } + private boolean objectHas(String world, Group group, String permission) { if (group == null) return false; @@ -101,7 +117,7 @@ public class VaultPermissionHook extends Permission { return toApply.containsKey(permission) && toApply.get(permission); } - private boolean objectAdd(String world, PermissionHolder object, String permission) { + private boolean add(String world, PermissionHolder object, String permission) { if (object == null) return false; try { @@ -112,11 +128,11 @@ public class VaultPermissionHook extends Permission { } } catch (ObjectAlreadyHasException ignored) {} - objectSave(object); + save(object); return true; } - private boolean objectRemove(String world, PermissionHolder object, String permission) { + private boolean remove(String world, PermissionHolder object, String permission) { if (object == null) return false; try { @@ -127,17 +143,18 @@ public class VaultPermissionHook extends Permission { } } catch (ObjectLacksException ignored) {} - objectSave(object); + save(object); return true; } - void objectSave(PermissionHolder t) { + void save(PermissionHolder t) { if (t instanceof User) { ((User) t).refreshPermissions(); - plugin.getDatastore().saveUser(((User) t), Callback.empty()); + plugin.getDatastore().saveUser(((User) t)); } if (t instanceof Group) { - plugin.getDatastore().saveGroup(((Group) t), c -> plugin.runUpdateTask()); + plugin.getDatastore().saveGroup(((Group) t)); + plugin.runUpdateTask(); } } @@ -165,14 +182,16 @@ public class VaultPermissionHook extends Permission { public boolean playerAdd(String world, @NonNull String player, @NonNull String permission) { log("Adding permission to player " + player + ": '" + permission + "' on world " + world + ", server " + server); final User user = plugin.getUserManager().get(player); - return objectAdd(world, user, permission); + scheduleTask(() -> add(world, user, permission)); + return true; } @Override public boolean playerRemove(String world, @NonNull String player, @NonNull String permission) { log("Removing permission from player " + player + ": '" + permission + "' on world " + world + ", server " + server); final User user = plugin.getUserManager().get(player); - return objectRemove(world, user, permission); + scheduleTask(() -> remove(world, user, permission)); + return true; } @Override @@ -186,14 +205,16 @@ public class VaultPermissionHook extends Permission { public boolean groupAdd(String world, @NonNull String groupName, @NonNull String permission) { log("Adding permission to group " + groupName + ": '" + permission + "' on world " + world + ", server " + server); final Group group = plugin.getGroupManager().get(groupName); - return objectAdd(world, group, permission); + scheduleTask(() -> add(world, group, permission)); + return true; } @Override public boolean groupRemove(String world, @NonNull String groupName, @NonNull String permission) { log("Removing permission from group " + groupName + ": '" + permission + "' on world " + world + ", server " + server); final Group group = plugin.getGroupManager().get(groupName); - return objectRemove(world, group, permission); + scheduleTask(() -> remove(world, group, permission)); + return true; } @Override @@ -213,41 +234,47 @@ public class VaultPermissionHook extends Permission { @Override public boolean playerAddGroup(String world, @NonNull String player, @NonNull String groupName) { - log("Adding player " + player + " to group: '" + groupName + "' on world " + world + ", server " + server); final User user = plugin.getUserManager().get(player); if (user == null) return false; final Group group = plugin.getGroupManager().get(groupName); if (group == null) return false; - try { - if (world != null && !world.equals("")) { - user.addGroup(group, server, world); - } else { - user.addGroup(group, server); - } - } catch (ObjectAlreadyHasException ignored) {} - objectSave(user); + scheduleTask(() -> { + log("Adding player " + player + " to group: '" + groupName + "' on world " + world + ", server " + server); // todo move + try { + if (world != null && !world.equals("")) { + user.addGroup(group, server, world); + } else { + user.addGroup(group, server); + } + } catch (ObjectAlreadyHasException ignored) {} + save(user); + }); return true; } @Override public boolean playerRemoveGroup(String world, @NonNull String player, @NonNull String groupName) { - log("Removing player " + player + " from group: '" + groupName + "' on world " + world + ", server " + server); final User user = plugin.getUserManager().get(player); if (user == null) return false; final Group group = plugin.getGroupManager().get(groupName); if (group == null) return false; - try { - if (world != null && !world.equals("")) { - user.removeGroup(group, server, world); - } else { - user.removeGroup(group, server); - } - } catch (ObjectLacksException ignored) {} - objectSave(user); + scheduleTask(() -> { + log("Removing player " + player + " from group: '" + groupName + "' on world " + world + ", server " + server); // todo move + plugin.getLog().info("before: " + user.getNodes().toString()); + try { + if (world != null && !world.equals("")) { + user.removeGroup(group, server, world); + } else { + user.removeGroup(group, server); + } + } catch (ObjectLacksException ignored) {} + plugin.getLog().info("after: " + user.getNodes().toString()); + save(user); + }); return true; } diff --git a/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java b/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java index 7e1874f2..44a23f9d 100644 --- a/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java +++ b/common/src/main/java/me/lucko/luckperms/api/implementation/internal/PermissionHolderLink.java @@ -39,7 +39,7 @@ import static me.lucko.luckperms.core.PermissionHolder.exportToLegacy; /** * Provides a link between {@link PermissionHolder} and {@link me.lucko.luckperms.core.PermissionHolder} */ -@SuppressWarnings({"unused", "deprecation"}) +@SuppressWarnings("unused") @AllArgsConstructor public class PermissionHolderLink implements PermissionHolder { @@ -58,12 +58,12 @@ public class PermissionHolderLink implements PermissionHolder { @Override public Set getEnduringPermissions() { - return Collections.unmodifiableSet(master.getNodes()); + return master.getNodes(); } @Override public Set getTransientPermissions() { - return Collections.unmodifiableSet(master.getTransientNodes()); + return master.getTransientNodes(); } @Override @@ -233,22 +233,36 @@ public class PermissionHolderLink implements PermissionHolder { @Override public Map getLocalPermissions(String server, String world, List excludedGroups, List possibleNodes) { - return master.getLocalPermissions(server, world, excludedGroups, possibleNodes); + Map context = new HashMap<>(); + if (server != null && !server.equals("")) { + context.put("server", server); + } + if (world != null && !world.equals("")) { + context.put("world", world); + } + return master.exportNodes(new Contexts(context, true, true, true, true, true), Collections.emptyList(), false); } @Override public Map getLocalPermissions(String server, String world, List excludedGroups) { - return master.getLocalPermissions(server, world, excludedGroups); + Map context = new HashMap<>(); + if (server != null && !server.equals("")) { + context.put("server", server); + } + if (world != null && !world.equals("")) { + context.put("world", world); + } + return master.exportNodes(new Contexts(context, true, true, true, true, true), Collections.emptyList(), false); } @Override public Map getLocalPermissions(String server, List excludedGroups, List possibleNodes) { - return master.getLocalPermissions(server, excludedGroups, possibleNodes); + return getLocalPermissions(server, null, excludedGroups, possibleNodes); } @Override public Map getLocalPermissions(String server, List excludedGroups) { - return master.getLocalPermissions(server, excludedGroups); + return getLocalPermissions(server, null, excludedGroups, null); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupBulkChange.java b/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupBulkChange.java index aa81f8ce..568f04e5 100644 --- a/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupBulkChange.java +++ b/common/src/main/java/me/lucko/luckperms/commands/group/subcommands/GroupBulkChange.java @@ -27,6 +27,8 @@ import me.lucko.luckperms.api.Node; import me.lucko.luckperms.commands.*; import me.lucko.luckperms.constants.Message; import me.lucko.luckperms.constants.Permission; +import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; +import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.groups.Group; import java.util.HashSet; @@ -55,6 +57,7 @@ public class GroupBulkChange extends SubCommand { } Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); if (!type.equals("world") && !type.equals("server")) { Message.BULK_CHANGE_TYPE_ERROR.send(sender); @@ -70,7 +73,7 @@ public class GroupBulkChange extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setWorld(to).build()); } } else { @@ -81,12 +84,23 @@ public class GroupBulkChange extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setServer(to).build()); } } - group.getNodes().addAll(toAdd); + toRemove.forEach(n -> { + try { + group.unsetPermission(n); + } catch (ObjectLacksException ignored) {} + }); + + toAdd.forEach(n -> { + try { + group.setPermission(n); + } catch (ObjectAlreadyHasException ignored) {} + }); + save(group, sender, plugin); Message.BULK_CHANGE_SUCCESS.send(sender, toAdd.size()); return CommandResult.SUCCESS; diff --git a/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserBulkChange.java b/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserBulkChange.java index 53d063c5..cd3d5662 100644 --- a/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserBulkChange.java +++ b/common/src/main/java/me/lucko/luckperms/commands/user/subcommands/UserBulkChange.java @@ -27,6 +27,8 @@ import me.lucko.luckperms.api.Node; import me.lucko.luckperms.commands.*; import me.lucko.luckperms.constants.Message; import me.lucko.luckperms.constants.Permission; +import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; +import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.users.User; import java.util.HashSet; @@ -55,6 +57,7 @@ public class UserBulkChange extends SubCommand { } Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); if (!type.equals("world") && !type.equals("server")) { Message.BULK_CHANGE_TYPE_ERROR.send(sender); @@ -75,7 +78,7 @@ public class UserBulkChange extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setWorld(to).build()); } } else { @@ -91,12 +94,23 @@ public class UserBulkChange extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setServer(to).build()); } } - user.getNodes().addAll(toAdd); + toRemove.forEach(n -> { + try { + user.unsetPermission(n); + } catch (ObjectLacksException ignored) {} + }); + + toAdd.forEach(n -> { + try { + user.setPermission(n); + } catch (ObjectAlreadyHasException ignored) {} + }); + save(user, sender, plugin); Message.BULK_CHANGE_SUCCESS.send(sender, toAdd.size()); return CommandResult.SUCCESS; diff --git a/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditGroup.java b/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditGroup.java index 176c9add..93be422e 100644 --- a/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditGroup.java +++ b/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditGroup.java @@ -27,6 +27,8 @@ import me.lucko.luckperms.api.Node; import me.lucko.luckperms.commands.*; import me.lucko.luckperms.constants.Message; import me.lucko.luckperms.constants.Permission; +import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; +import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.storage.Datastore; import me.lucko.luckperms.users.User; @@ -70,6 +72,7 @@ public class BulkEditGroup extends SubCommand { } Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); Iterator iterator = user.getNodes().iterator(); if (type.equals("world")) { while (iterator.hasNext()) { @@ -92,7 +95,7 @@ public class BulkEditGroup extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setWorld(to).build()); } } else { @@ -116,12 +119,23 @@ public class BulkEditGroup extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setServer(to).build()); } } - user.getNodes().addAll(toAdd); + toRemove.forEach(n -> { + try { + user.unsetPermission(n); + } catch (ObjectLacksException ignored) {} + }); + + toAdd.forEach(n -> { + try { + user.setPermission(n); + } catch (ObjectAlreadyHasException ignored) {} + }); + plugin.getUserManager().cleanup(user); plugin.getDatastore().saveUser(user); } diff --git a/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditPermission.java b/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditPermission.java index 96be94fd..c7204cb4 100644 --- a/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditPermission.java +++ b/common/src/main/java/me/lucko/luckperms/commands/usersbulkedit/subcommands/BulkEditPermission.java @@ -27,6 +27,8 @@ import me.lucko.luckperms.api.Node; import me.lucko.luckperms.commands.*; import me.lucko.luckperms.constants.Message; import me.lucko.luckperms.constants.Permission; +import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; +import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.storage.Datastore; import me.lucko.luckperms.users.User; @@ -69,6 +71,7 @@ public class BulkEditPermission extends SubCommand { } Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); Iterator iterator = user.getNodes().iterator(); if (type.equals("world")) { while (iterator.hasNext()) { @@ -87,7 +90,7 @@ public class BulkEditPermission extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setWorld(to).build()); } } else { @@ -107,12 +110,23 @@ public class BulkEditPermission extends SubCommand { continue; } - iterator.remove(); + toRemove.add(element); toAdd.add(me.lucko.luckperms.core.Node.builderFromExisting(element).setServer(to).build()); } } - user.getNodes().addAll(toAdd); + toRemove.forEach(n -> { + try { + user.unsetPermission(n); + } catch (ObjectLacksException ignored) {} + }); + + toAdd.forEach(n -> { + try { + user.setPermission(n); + } catch (ObjectAlreadyHasException ignored) {} + }); + plugin.getUserManager().cleanup(user); plugin.getDatastore().saveUser(user); } diff --git a/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java index 73048830..d24c8acd 100644 --- a/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/core/PermissionHolder.java @@ -22,6 +22,8 @@ package me.lucko.luckperms.core; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -37,11 +39,13 @@ import me.lucko.luckperms.contexts.Contexts; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.groups.Group; +import me.lucko.luckperms.utils.Cache; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -64,46 +68,138 @@ public abstract class PermissionHolder { @Getter(AccessLevel.PROTECTED) private final LuckPermsPlugin plugin; - /** - * The user/group's permissions - */ - @Getter - private Set nodes = ConcurrentHashMap.newKeySet(); + private final Set nodes = new HashSet<>(); + private final Set transientNodes = new HashSet<>(); - /** - * The user/group's transient permissions - */ - @Getter - private Set transientNodes = ConcurrentHashMap.newKeySet(); + private Cache> cache = new Cache<>(); + private Cache> mergedCache = new Cache<>(); + private Cache> enduringCache = new Cache<>(); + private Cache> transientCache = new Cache<>(); @Getter private final Lock ioLock = new ReentrantLock(); + private void invalidateCache(boolean enduring) { + if (enduring) { + enduringCache.invalidate(); + } else { + transientCache.invalidate(); + } + cache.invalidate(); + mergedCache.invalidate(); + } + + public Set getNodes() { + synchronized (nodes) { + return enduringCache.get(() -> ImmutableSet.copyOf(nodes)); + } + } + + public Set getTransientNodes() { + synchronized (transientNodes) { + return transientCache.get(() -> ImmutableSet.copyOf(transientNodes)); + } + } + + public void setNodes(Set nodes) { + synchronized (this.nodes) { + if (!this.nodes.equals(nodes)) { + invalidateCache(true); + } + + this.nodes.clear(); + this.nodes.addAll(nodes); + } + + auditTemporaryPermissions(); + } + + public void setTransientNodes(Set nodes) { + synchronized (this.transientNodes) { + if (!this.transientNodes.equals(nodes)) { + invalidateCache(false); + } + + this.transientNodes.clear(); + this.transientNodes.addAll(nodes); + } + + auditTemporaryPermissions(); + } + + @Deprecated + public void setNodes(Map nodes) { + synchronized (this.nodes) { + if (!this.nodes.equals(nodes)) { + invalidateCache(true); + } + + this.nodes.clear(); + this.nodes.addAll(nodes.entrySet().stream() + .map(e -> me.lucko.luckperms.core.Node.fromSerialisedNode(e.getKey(), e.getValue())) + .collect(Collectors.toList())); + } + + auditTemporaryPermissions(); + } + + public void addNodeUnchecked(Node node) { + synchronized (nodes) { + nodes.add(node); + invalidateCache(true); + } + } + /** - * Returns a Set of nodes in priority order + * Clear all of the holders permission nodes + */ + public void clearNodes() { + synchronized (nodes) { + nodes.clear(); + invalidateCache(true); + } + } + + public void clearTransientNodes() { + synchronized (transientNodes) { + transientNodes.clear(); + invalidateCache(false); + } + } + + /** + * Combines and returns this holders nodes in a priority order. * @return the holders transient and permanent nodes */ public SortedSet getPermissions(boolean mergeTemp) { - // Returns no duplicate nodes. as in, nodes with the same value. + Supplier> supplier = () -> { + TreeSet combined = new TreeSet<>(PriorityComparator.reverse()); - TreeSet combined = new TreeSet<>(PriorityComparator.reverse()); - nodes.stream().map(n -> LocalizedNode.of(n, getObjectName())).forEach(combined::add); - transientNodes.stream().map(n -> LocalizedNode.of(n, getObjectName())).forEach(combined::add); + getNodes().stream() + .map(n -> LocalizedNode.of(n, getObjectName())) + .forEach(combined::add); - TreeSet permissions = new TreeSet<>(PriorityComparator.reverse()); + getTransientNodes().stream() + .map(n -> LocalizedNode.of(n, getObjectName())) + .forEach(combined::add); - combined: - for (LocalizedNode node : combined) { - for (LocalizedNode other : permissions) { - if (mergeTemp ? node.getNode().equalsIgnoringValueOrTemp(other.getNode()) : node.getNode().almostEquals(other.getNode())) { - continue combined; + TreeSet permissions = new TreeSet<>(PriorityComparator.reverse()); + + combined: + for (LocalizedNode node : combined) { + for (LocalizedNode other : permissions) { + if (mergeTemp ? node.getNode().equalsIgnoringValueOrTemp(other.getNode()) : node.getNode().almostEquals(other.getNode())) { + continue combined; + } } + + permissions.add(node); } - permissions.add(node); - } + return permissions; + }; - return permissions; + return mergeTemp ? mergedCache.get(supplier) : cache.get(supplier); } /** @@ -111,33 +207,44 @@ public abstract class PermissionHolder { * @return true if permissions had expired and were removed */ public boolean auditTemporaryPermissions() { - final boolean[] work = {false}; + boolean work = false; final PermissionHolder instance = this; - nodes.removeIf(node -> { - if (node.hasExpired()) { - work[0] = true; - plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(instance), node)); - return true; + synchronized (nodes) { + boolean w = nodes.removeIf(node -> { + if (node.hasExpired()) { + plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(instance), node)); + return true; + } + return false; + }); + if (w) { + invalidateCache(true); + work = true; } - return false; - }); + } - transientNodes.removeIf(node -> { - if (node.hasExpired()) { - work[0] = true; - plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(instance), node)); - return true; + synchronized (transientNodes) { + boolean w = transientNodes.removeIf(node -> { + if (node.hasExpired()) { + plugin.getApiProvider().fireEventAsync(new PermissionNodeExpireEvent(new PermissionHolderLink(instance), node)); + return true; + } + return false; + }); + if (w) { + invalidateCache(false); + work = true; } - return false; - }); + } - return work[0]; + return work; } /** - * Gets all of the nodes that this holder has and inherits + * 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 SortedSet getAllNodes(List excludedGroups, Contexts context) { @@ -197,7 +304,6 @@ public abstract class PermissionHolder { * @return a map of permissions */ public Set getAllNodesFiltered(Contexts context) { - Set perms = ConcurrentHashMap.newKeySet(); SortedSet allNodes; if (context.isApplyGroups()) { @@ -212,24 +318,19 @@ public abstract class PermissionHolder { contexts.remove("server"); contexts.remove("world"); + allNodes.removeIf(node -> + !node.shouldApplyOnServer(server, context.isIncludeGlobal(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), plugin.getConfiguration().isApplyingRegex()) || + !node.shouldApplyWithContext(contexts, false) + ); + + Set perms = ConcurrentHashMap.newKeySet(); + all: for (LocalizedNode ln : allNodes) { - Node node = ln.getNode(); - if (!node.shouldApplyOnServer(server, context.isIncludeGlobal(), plugin.getConfiguration().isApplyingRegex())) { - continue; - } - - if (!node.shouldApplyOnWorld(world, context.isIncludeGlobalWorld(), plugin.getConfiguration().isApplyingRegex())) { - continue; - } - - if (!node.shouldApplyWithContext(contexts, false)) { - continue; - } - // Force higher priority nodes to override for (LocalizedNode alreadyIn : perms) { - if (node.getPermission().equals(alreadyIn.getNode().getPermission())) { + if (ln.getNode().getPermission().equals(alreadyIn.getNode().getPermission())) { continue all; } } @@ -278,43 +379,7 @@ public abstract class PermissionHolder { } } - return Collections.unmodifiableMap(perms); - } - - public void setNodes(Set nodes) { - this.nodes.clear(); - this.nodes.addAll(nodes); - auditTemporaryPermissions(); - } - - public void setTransiestNodes(Set nodes) { - this.transientNodes.clear(); - this.transientNodes.addAll(nodes); - auditTemporaryPermissions(); - } - - public static Map exportToLegacy(Set nodes) { - Map m = new HashMap<>(); - for (Node node : nodes) { - m.put(node.toSerializedNode(), node.getValue()); - } - return Collections.unmodifiableMap(m); - } - - // Convenience method - private static Node.Builder buildNode(String permission) { - return new me.lucko.luckperms.core.Node.Builder(permission); - } - - @Deprecated - public void setNodes(Map nodes) { - this.nodes.clear(); - - this.nodes.addAll(nodes.entrySet().stream() - .map(e -> me.lucko.luckperms.core.Node.fromSerialisedNode(e.getKey(), e.getValue())) - .collect(Collectors.toList())); - - auditTemporaryPermissions(); + return ImmutableMap.copyOf(perms); } /** @@ -324,7 +389,7 @@ public abstract class PermissionHolder { * @return a tristate */ public Tristate hasPermission(Node node, boolean t) { - for (Node n : t ? transientNodes : nodes) { + for (Node n : t ? getTransientNodes() : getNodes()) { if (n.almostEquals(node)) { return n.getTristate(); } @@ -419,7 +484,11 @@ public abstract class PermissionHolder { throw new ObjectAlreadyHasException(); } - nodes.add(node); + synchronized (nodes) { + nodes.add(node); + invalidateCache(true); + } + plugin.getApiProvider().fireEventAsync(new PermissionNodeSetEvent(new PermissionHolderLink(this), node)); } @@ -433,7 +502,11 @@ public abstract class PermissionHolder { throw new ObjectAlreadyHasException(); } - transientNodes.add(node); + synchronized (transientNodes) { + transientNodes.add(node); + invalidateCache(false); + } + plugin.getApiProvider().fireEventAsync(new PermissionNodeSetEvent(new PermissionHolderLink(this), node)); } @@ -471,7 +544,10 @@ public abstract class PermissionHolder { throw new ObjectLacksException(); } - nodes.removeIf(e -> e.almostEquals(node)); + synchronized (nodes) { + nodes.removeIf(e -> e.almostEquals(node)); + invalidateCache(true); + } if (node.isGroupNode()) { plugin.getApiProvider().fireEventAsync(new GroupRemoveEvent(new PermissionHolderLink(this), @@ -491,7 +567,10 @@ public abstract class PermissionHolder { throw new ObjectLacksException(); } - transientNodes.removeIf(e -> e.almostEquals(node)); + synchronized (transientNodes) { + transientNodes.removeIf(e -> e.almostEquals(node)); + invalidateCache(false); + } if (node.isGroupNode()) { plugin.getApiProvider().fireEventAsync(new GroupRemoveEvent(new PermissionHolderLink(this), @@ -578,43 +657,15 @@ public abstract class PermissionHolder { .collect(Collectors.toList()); } - /* - * Don't use these methods, only here for compat reasons - */ - - @Deprecated - public Map getLocalPermissions(String server, String world, List excludedGroups, List possibleNodes) { - Map context = new HashMap<>(); - if (server != null && !server.equals("")) { - context.put("server", server); + public static Map exportToLegacy(Set nodes) { + ImmutableMap.Builder m = ImmutableMap.builder(); + for (Node node : nodes) { + m.put(node.toSerializedNode(), node.getValue()); } - if (world != null && !world.equals("")) { - context.put("world", world); - } - return exportNodes(new Contexts(context, plugin.getConfiguration().isIncludingGlobalPerms(), true, true, true, true), Collections.emptyList(), false); + return m.build(); } - @Deprecated - public Map getLocalPermissions(String server, String world, List excludedGroups) { - Map context = new HashMap<>(); - if (server != null && !server.equals("")) { - context.put("server", server); - } - if (world != null && !world.equals("")) { - context.put("world", world); - } - return exportNodes(new Contexts(context, plugin.getConfiguration().isIncludingGlobalPerms(), true, true, true, true), Collections.emptyList(), false); - } - - @SuppressWarnings("deprecation") - @Deprecated - public Map getLocalPermissions(String server, List excludedGroups, List possibleNodes) { - return getLocalPermissions(server, null, excludedGroups, possibleNodes); - } - - @SuppressWarnings("deprecation") - @Deprecated - public Map getLocalPermissions(String server, List excludedGroups) { - return getLocalPermissions(server, null, excludedGroups, null); + private static Node.Builder buildNode(String permission) { + return new me.lucko.luckperms.core.Node.Builder(permission); } } diff --git a/common/src/main/java/me/lucko/luckperms/groups/Group.java b/common/src/main/java/me/lucko/luckperms/groups/Group.java index 178811b6..e1dcdc93 100644 --- a/common/src/main/java/me/lucko/luckperms/groups/Group.java +++ b/common/src/main/java/me/lucko/luckperms/groups/Group.java @@ -248,11 +248,4 @@ public class Group extends PermissionHolder implements Identifiable { public void unsetInheritGroup(Group group, String server, String world, boolean temporary) throws ObjectLacksException { unsetPermission("group." + group.getName(), server, world, temporary); } - - /** - * Clear all of the groups permission nodes - */ - public void clearNodes() { - getNodes().clear(); - } } diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java index 240cbe18..9a3419cc 100644 --- a/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java +++ b/common/src/main/java/me/lucko/luckperms/storage/methods/JSONDatastore.java @@ -79,6 +79,7 @@ public class JSONDatastore extends FlatfileDatastore { public boolean loadUser(UUID uuid, String username) { User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username)); user.getIoLock().lock(); + plugin.getLog().info("#loadUser for: " + user.getName()); try { return call(() -> { File userFile = new File(usersDir, uuid.toString() + ".json"); @@ -96,7 +97,7 @@ public class JSONDatastore extends FlatfileDatastore { while (reader.hasNext()) { String node = reader.nextName(); boolean b = reader.nextBoolean(); - user.getNodes().add(Node.fromSerialisedNode(node, b)); + user.addNodeUnchecked(Node.fromSerialisedNode(node, b)); } reader.endObject(); reader.endObject(); @@ -139,6 +140,7 @@ public class JSONDatastore extends FlatfileDatastore { } }, false); } finally { + plugin.getLog().info("#loadUser finished for: " + user.getName()); user.getIoLock().unlock(); } } @@ -146,6 +148,7 @@ public class JSONDatastore extends FlatfileDatastore { @Override public boolean saveUser(User user) { user.getIoLock().lock(); + plugin.getLog().info("#saveUser for: " + user.getName()); try { return call(() -> { File userFile = new File(usersDir, user.getUuid().toString() + ".json"); @@ -173,6 +176,7 @@ public class JSONDatastore extends FlatfileDatastore { writer.name("perms"); writer.beginObject(); for (Map.Entry e : exportToLegacy(user.getNodes()).entrySet()) { + plugin.getLog().info("entry: " + e.toString()); writer.name(e.getKey()).value(e.getValue().booleanValue()); } writer.endObject(); @@ -181,6 +185,7 @@ public class JSONDatastore extends FlatfileDatastore { }); }, false); } finally { + plugin.getLog().info("#saveUser ended for: " + user.getName()); user.getIoLock().unlock(); } } @@ -257,7 +262,7 @@ public class JSONDatastore extends FlatfileDatastore { while (reader.hasNext()) { String node = reader.nextName(); boolean b = reader.nextBoolean(); - group.getNodes().add(Node.fromSerialisedNode(node, b)); + group.addNodeUnchecked(Node.fromSerialisedNode(node, b)); } reader.endObject(); @@ -307,7 +312,7 @@ public class JSONDatastore extends FlatfileDatastore { while (reader.hasNext()) { String node = reader.nextName(); boolean b = reader.nextBoolean(); - group.getNodes().add(Node.fromSerialisedNode(node, b)); + group.addNodeUnchecked(Node.fromSerialisedNode(node, b)); } reader.endObject(); reader.endObject(); diff --git a/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java b/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java index b52c4d66..f3d58827 100644 --- a/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java +++ b/common/src/main/java/me/lucko/luckperms/storage/methods/YAMLDatastore.java @@ -92,7 +92,7 @@ public class YAMLDatastore extends FlatfileDatastore { user.setPrimaryGroup((String) values.get("primary-group")); Map perms = (Map) values.get("perms"); for (Map.Entry e : perms.entrySet()) { - user.getNodes().add(Node.fromSerialisedNode(e.getKey(), e.getValue())); + user.addNodeUnchecked(Node.fromSerialisedNode(e.getKey(), e.getValue())); } boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); @@ -214,7 +214,7 @@ public class YAMLDatastore extends FlatfileDatastore { return doRead(groupFile, values -> { Map perms = (Map) values.get("perms"); for (Map.Entry e : perms.entrySet()) { - group.getNodes().add(Node.fromSerialisedNode(e.getKey(), e.getValue())); + group.addNodeUnchecked(Node.fromSerialisedNode(e.getKey(), e.getValue())); } return true; }); @@ -247,7 +247,7 @@ public class YAMLDatastore extends FlatfileDatastore { return groupFile.exists() && doRead(groupFile, values -> { Map perms = (Map) values.get("perms"); for (Map.Entry e : perms.entrySet()) { - group.getNodes().add(Node.fromSerialisedNode(e.getKey(), e.getValue())); + group.addNodeUnchecked(Node.fromSerialisedNode(e.getKey(), e.getValue())); } return true; }); diff --git a/common/src/main/java/me/lucko/luckperms/users/User.java b/common/src/main/java/me/lucko/luckperms/users/User.java index ed04fb7e..33a8ceb8 100644 --- a/common/src/main/java/me/lucko/luckperms/users/User.java +++ b/common/src/main/java/me/lucko/luckperms/users/User.java @@ -249,8 +249,9 @@ public abstract class User extends PermissionHolder implements Identifiable + * + * 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.utils; + +import java.util.function.Supplier; + +public class Cache { + private T t = null; + + public T get(Supplier supplier) { + synchronized (this) { + if (t == null) { + t = supplier.get(); + } + return t; + } + } + + public void invalidate() { + synchronized (this) { + t = null; + } + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubject.java b/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubject.java index 506065fb..58097b1b 100644 --- a/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubject.java @@ -23,17 +23,12 @@ package me.lucko.luckperms.api.sponge; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; import me.lucko.luckperms.api.Node; -import me.lucko.luckperms.api.data.Callback; import me.lucko.luckperms.contexts.Contexts; import me.lucko.luckperms.core.PermissionHolder; -import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; -import me.lucko.luckperms.exceptions.ObjectLacksException; import me.lucko.luckperms.groups.Group; import me.lucko.luckperms.users.User; import org.spongepowered.api.command.CommandSource; @@ -46,7 +41,6 @@ import org.spongepowered.api.util.Tristate; import java.util.*; import java.util.stream.Collectors; -import static me.lucko.luckperms.utils.ArgumentChecker.escapeCharacters; import static me.lucko.luckperms.utils.ArgumentChecker.unescapeCharacters; @EqualsAndHashCode(of = {"holder"}) @@ -57,25 +51,28 @@ public class LuckPermsSubject implements Subject { @Getter private final PermissionHolder holder; - private final EnduringData enduringData; - private final TransientData transientData; + private final LuckPermsSubjectData enduringData; + private final LuckPermsSubjectData transientData; protected final LuckPermsService service; LuckPermsSubject(PermissionHolder holder, LuckPermsService service) { this.holder = holder; - this.enduringData = new EnduringData(this, service, holder); - this.transientData = new TransientData(service, holder); + this.enduringData = new LuckPermsSubjectData(true, this, service, holder); + this.transientData = new LuckPermsSubjectData(true, this, service, holder); this.service = service; } - private void objectSave(PermissionHolder t) { - if (t instanceof User) { - ((User) t).refreshPermissions(); - service.getPlugin().getDatastore().saveUser(((User) t), Callback.empty()); - } - if (t instanceof Group) { - service.getPlugin().getDatastore().saveGroup(((Group) t), c -> service.getPlugin().runUpdateTask()); - } + void objectSave(PermissionHolder t) { + service.getPlugin().doAsync(() -> { + if (t instanceof User) { + ((User) t).refreshPermissions(); + service.getPlugin().getDatastore().saveUser(((User) t)); + } + if (t instanceof Group) { + service.getPlugin().getDatastore().saveGroup(((Group) t)); + service.getPlugin().runUpdateTask(); + } + }); } @Override @@ -162,14 +159,14 @@ public class LuckPermsSubject implements Subject { @Override public Optional getOption(Set set, String s) { if (s.equalsIgnoreCase("prefix")) { - String prefix = getChatMeta(true, holder); + String prefix = getChatMeta(set, true, holder); if (!prefix.equals("")) { return Optional.of(prefix); } } if (s.equalsIgnoreCase("suffix")) { - String suffix = getChatMeta(false, holder); + String suffix = getChatMeta(set, false, holder); if (!suffix.equals("")) { return Optional.of(suffix); } @@ -198,9 +195,15 @@ public class LuckPermsSubject implements Subject { return SubjectData.GLOBAL_CONTEXT; } - private String getChatMeta(boolean prefix, PermissionHolder holder) { + private String getChatMeta(Set contexts, boolean prefix, PermissionHolder holder) { if (holder == null) return ""; + Map context = contexts.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + String server = context.get("server"); + String world = context.get("world"); + context.remove("server"); + context.remove("world"); + int priority = Integer.MIN_VALUE; String meta = null; @@ -213,15 +216,17 @@ public class LuckPermsSubject implements Subject { continue; } - if (!n.shouldApplyOnServer(service.getPlugin().getConfiguration().getVaultServer(), service.getPlugin().getConfiguration().isVaultIncludingGlobal(), false)) { + if (!n.shouldApplyOnServer(server, service.getPlugin().getConfiguration().isVaultIncludingGlobal(), false)) { continue; } - /* TODO per world - if (!n.shouldApplyOnWorld(world, service.getPlugin().getConfiguration().getVaultIncludeGlobal(), false)) { + if (!n.shouldApplyOnWorld(world, true, false)) { + continue; + } + + if (!n.shouldApplyWithContext(context, false)) { continue; } - */ Map.Entry value = prefix ? n.getPrefix() : n.getSuffix(); if (value.getKey() > priority) { @@ -232,650 +237,4 @@ public class LuckPermsSubject implements Subject { return meta == null ? "" : unescapeCharacters(meta); } - - @AllArgsConstructor - public static class EnduringData implements SubjectData { - private final LuckPermsSubject superClass; - private final LuckPermsService service; - - @Getter - private final PermissionHolder holder; - - @Override - public Map, Map> getAllPermissions() { - Map, Map> perms = new HashMap<>(); - - for (Node n : holder.getNodes()) { - Set contexts = n.getExtraContexts().entrySet().stream() - .map(entry -> new Context(entry.getKey(), entry.getValue())) - .collect(Collectors.toSet()); - - if (n.isServerSpecific()) { - contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); - } - - if (n.isWorldSpecific()) { - contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); - } - - if (!perms.containsKey(contexts)) { - perms.put(contexts, new HashMap<>()); - } - - perms.get(contexts).put(n.getPermission(), n.getValue()); - } - - return ImmutableMap.copyOf(perms); - } - - @Override - public Map getPermissions(Set set) { - return ImmutableMap.copyOf(getAllPermissions().getOrDefault(set, Collections.emptyMap())); - } - - @Override - public boolean setPermission(Set set, String s, Tristate tristate) { - if (tristate == Tristate.UNDEFINED) { - // Unset - Node.Builder builder = new me.lucko.luckperms.core.Node.Builder(s); - - for (Context ct : set) { - builder.withExtraContext(ct.getKey(), ct.getValue()); - } - - try { - holder.unsetPermission(builder.build()); - } catch (ObjectLacksException ignored) {} - superClass.objectSave(holder); - return true; - } - - Node.Builder builder = new me.lucko.luckperms.core.Node.Builder(s) - .setValue(tristate.asBoolean()); - - for (Context ct : set) { - builder.withExtraContext(ct.getKey(), ct.getValue()); - } - - try { - holder.setPermission(builder.build()); - } catch (ObjectAlreadyHasException ignored) {} - superClass.objectSave(holder); - return true; - } - - @Override - public boolean clearPermissions() { - holder.getNodes().clear(); - if (holder instanceof User) { - service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); - } - superClass.objectSave(holder); - return true; - } - - @Override - public boolean clearPermissions(Set set) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - boolean work = false; - Iterator iterator = holder.getNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - if (entry.shouldApplyWithContext(context)) { - iterator.remove(); - work = true; - } - } - - if (holder instanceof User) { - service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); - } - - superClass.objectSave(holder); - return work; - } - - @Override - public Map, List> getAllParents() { - Map, List> parents = new HashMap<>(); - - for (Node n : holder.getAllNodes(null, Contexts.allowAll())) { - if (!n.isGroupNode()) { - continue; - } - - Set contexts = n.getExtraContexts().entrySet().stream() - .map(entry -> new Context(entry.getKey(), entry.getValue())) - .collect(Collectors.toSet()); - - if (n.isServerSpecific()) { - contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); - } - - if (n.isWorldSpecific()) { - contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); - } - - if (!parents.containsKey(contexts)) { - parents.put(contexts, new ArrayList<>()); - } - - parents.get(contexts).add(service.getGroupSubjects().get(n.getGroupName())); - } - - return ImmutableMap.copyOf(parents); - } - - @Override - public List getParents(Set contexts) { - return ImmutableList.copyOf(getAllParents().getOrDefault(contexts, Collections.emptyList())); - } - - @Override - public boolean addParent(Set set, Subject subject) { - if (subject instanceof LuckPermsSubject) { - LuckPermsSubject permsSubject = ((LuckPermsSubject) subject); - - Map contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); - - try { - holder.setPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) - .withExtraContext(contexts) - .build()); - } catch (ObjectAlreadyHasException ignored) {} - superClass.objectSave(holder); - } else { - return false; - } - return false; - } - - @Override - public boolean removeParent(Set set, Subject subject) { - if (subject instanceof LuckPermsSubject) { - LuckPermsSubject permsSubject = ((LuckPermsSubject) subject); - - Map contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); - - try { - holder.unsetPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) - .withExtraContext(contexts) - .build()); - } catch (ObjectLacksException ignored) {} - superClass.objectSave(holder); - } else { - return false; - } - return false; - } - - @Override - public boolean clearParents() { - boolean work = false; - Iterator iterator = holder.getNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (entry.isGroupNode()) { - iterator.remove(); - work = true; - } - } - - if (holder instanceof User) { - service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); - } - - superClass.objectSave(holder); - return work; - } - - @Override - public boolean clearParents(Set set) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - boolean work = false; - Iterator iterator = holder.getNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (!entry.isGroupNode()) { - continue; - } - - if (entry.shouldApplyWithContext(context)) { - iterator.remove(); - work = true; - } - } - - if (holder instanceof User) { - service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); - } - - superClass.objectSave(holder); - return work; - } - - @Override - public Map, Map> getAllOptions() { - Map, Map> options = new HashMap<>(); - - for (Node n : holder.getAllNodes(null, Contexts.allowAll())) { - if (!n.isMeta()) { - continue; - } - - Set contexts = n.getExtraContexts().entrySet().stream() - .map(entry -> new Context(entry.getKey(), entry.getValue())) - .collect(Collectors.toSet()); - - if (n.isServerSpecific()) { - contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); - } - - if (n.isWorldSpecific()) { - contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); - } - - if (!options.containsKey(contexts)) { - options.put(contexts, new HashMap<>()); - } - - options.get(contexts).put(unescapeCharacters(n.getMeta().getKey()), unescapeCharacters(n.getMeta().getValue())); - } - - return ImmutableMap.copyOf(options); - } - - @Override - public Map getOptions(Set set) { - return ImmutableMap.copyOf(getAllOptions().getOrDefault(set, Collections.emptyMap())); - } - - @Override - public boolean setOption(Set set, String key, String value) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - key = escapeCharacters(key); - value = escapeCharacters(value); - - try { - holder.setPermission(new me.lucko.luckperms.core.Node.Builder("meta." + key + "." + value) - .withExtraContext(context) - .build() - ); - } catch (ObjectAlreadyHasException ignored) {} - superClass.objectSave(holder); - return true; - } - - @Override - public boolean clearOptions(Set set) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - boolean work = false; - Iterator iterator = holder.getNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (!entry.isMeta()) { - continue; - } - - if (entry.shouldApplyWithContext(context)) { - iterator.remove(); - work = true; - } - } - - superClass.objectSave(holder); - return work; - } - - @Override - public boolean clearOptions() { - boolean work = false; - Iterator iterator = holder.getNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (entry.isMeta()) { - iterator.remove(); - work = true; - } - } - - superClass.objectSave(holder); - return work; - } - } - - @AllArgsConstructor - public static class TransientData implements SubjectData { - private final LuckPermsService service; - - @Getter - private final PermissionHolder holder; - - @Override - public Map, Map> getAllPermissions() { - Map, Map> perms = new HashMap<>(); - - for (Node n : holder.getTransientNodes()) { - Set contexts = n.getExtraContexts().entrySet().stream() - .map(entry -> new Context(entry.getKey(), entry.getValue())) - .collect(Collectors.toSet()); - - if (n.isServerSpecific()) { - contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); - } - - if (n.isWorldSpecific()) { - contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); - } - - if (!perms.containsKey(contexts)) { - perms.put(contexts, new HashMap<>()); - } - - perms.get(contexts).put(n.getPermission(), n.getValue()); - } - - return ImmutableMap.copyOf(perms); - } - - @Override - public Map getPermissions(Set set) { - return ImmutableMap.copyOf(getAllPermissions().getOrDefault(set, Collections.emptyMap())); - } - - @Override - public boolean setPermission(Set set, String s, Tristate tristate) { - if (tristate == Tristate.UNDEFINED) { - // Unset - - Node.Builder builder = new me.lucko.luckperms.core.Node.Builder(s); - - for (Context ct : set) { - builder.withExtraContext(ct.getKey(), ct.getValue()); - } - - try { - holder.unsetTransientPermission(builder.build()); - } catch (ObjectLacksException ignored) {} - return true; - } - - Node.Builder builder = new me.lucko.luckperms.core.Node.Builder(s) - .setValue(tristate.asBoolean()); - - for (Context ct : set) { - builder.withExtraContext(ct.getKey(), ct.getValue()); - } - - try { - holder.setTransientPermission(builder.build()); - } catch (ObjectAlreadyHasException ignored) {} - return true; - } - - @Override - public boolean clearPermissions() { - holder.getTransientNodes().clear(); - return true; - } - - @Override - public boolean clearPermissions(Set set) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - boolean work = false; - Iterator iterator = holder.getTransientNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - if (entry.shouldApplyWithContext(context)) { - iterator.remove(); - work = true; - } - } - - return work; - } - - @Override - public Map, List> getAllParents() { - Map, List> parents = new HashMap<>(); - - for (Node n : holder.getTransientNodes()) { - if (!n.isGroupNode()) { - continue; - } - - Set contexts = n.getExtraContexts().entrySet().stream() - .map(entry -> new Context(entry.getKey(), entry.getValue())) - .collect(Collectors.toSet()); - - if (n.isServerSpecific()) { - contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); - } - - if (n.isWorldSpecific()) { - contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); - } - - if (!parents.containsKey(contexts)) { - parents.put(contexts, new ArrayList<>()); - } - - parents.get(contexts).add(service.getGroupSubjects().get(n.getGroupName())); - } - - return ImmutableMap.copyOf(parents); - } - - @Override - public List getParents(Set contexts) { - return ImmutableList.copyOf(getAllParents().getOrDefault(contexts, Collections.emptyList())); - } - - @Override - public boolean addParent(Set set, Subject subject) { - if (subject instanceof LuckPermsSubject) { - LuckPermsSubject permsSubject = ((LuckPermsSubject) subject); - - Map contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); - - try { - holder.setTransientPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) - .withExtraContext(contexts) - .build()); - } catch (ObjectAlreadyHasException ignored) {} - } else { - return false; - } - return false; - } - - @Override - public boolean removeParent(Set set, Subject subject) { - if (subject instanceof LuckPermsSubject) { - LuckPermsSubject permsSubject = ((LuckPermsSubject) subject); - - Map contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); - - try { - holder.unsetTransientPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) - .withExtraContext(contexts) - .build()); - } catch (ObjectLacksException ignored) {} - } else { - return false; - } - return false; - } - - @Override - public boolean clearParents() { - boolean work = false; - Iterator iterator = holder.getTransientNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (entry.isGroupNode()) { - iterator.remove(); - work = true; - } - } - - return work; - } - - @Override - public boolean clearParents(Set set) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - boolean work = false; - Iterator iterator = holder.getTransientNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (!entry.isGroupNode()) { - continue; - } - - if (entry.shouldApplyWithContext(context)) { - iterator.remove(); - work = true; - } - } - - return work; - } - - @Override - public Map, Map> getAllOptions() { - Map, Map> options = new HashMap<>(); - - for (Node n : holder.getTransientNodes()) { - if (!n.isMeta()) { - continue; - } - - Set contexts = n.getExtraContexts().entrySet().stream() - .map(entry -> new Context(entry.getKey(), entry.getValue())) - .collect(Collectors.toSet()); - - if (n.isServerSpecific()) { - contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); - } - - if (n.isWorldSpecific()) { - contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); - } - - if (!options.containsKey(contexts)) { - options.put(contexts, new HashMap<>()); - } - - options.get(contexts).put(unescapeCharacters(n.getMeta().getKey()), unescapeCharacters(n.getMeta().getValue())); - } - - return ImmutableMap.copyOf(options); - } - - @Override - public Map getOptions(Set set) { - return ImmutableMap.copyOf(getAllOptions().getOrDefault(set, Collections.emptyMap())); - } - - @Override - public boolean setOption(Set set, String key, String value) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - key = escapeCharacters(key); - value = escapeCharacters(value); - - try { - holder.setTransientPermission(new me.lucko.luckperms.core.Node.Builder("meta." + key + "." + value) - .withExtraContext(context) - .build() - ); - } catch (ObjectAlreadyHasException ignored) {} - return true; - } - - @Override - public boolean clearOptions(Set set) { - Map context = new HashMap<>(); - for (Context c : set) { - context.put(c.getKey(), c.getValue()); - } - - boolean work = false; - Iterator iterator = holder.getTransientNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (!entry.isMeta()) { - continue; - } - - if (entry.shouldApplyWithContext(context)) { - iterator.remove(); - work = true; - } - } - - return work; - } - - @Override - public boolean clearOptions() { - boolean work = false; - Iterator iterator = holder.getTransientNodes().iterator(); - - while (iterator.hasNext()) { - Node entry = iterator.next(); - - if (entry.isMeta()) { - iterator.remove(); - work = true; - } - } - - return work; - } - } } diff --git a/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubjectData.java new file mode 100644 index 00000000..377fa20b --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/api/sponge/LuckPermsSubjectData.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.api.sponge; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import lombok.AllArgsConstructor; +import lombok.Getter; +import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.core.PermissionHolder; +import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; +import me.lucko.luckperms.exceptions.ObjectLacksException; +import me.lucko.luckperms.users.User; +import org.spongepowered.api.service.context.Context; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.service.permission.SubjectData; +import org.spongepowered.api.util.Tristate; + +import java.util.*; +import java.util.stream.Collectors; + +import static me.lucko.luckperms.utils.ArgumentChecker.escapeCharacters; +import static me.lucko.luckperms.utils.ArgumentChecker.unescapeCharacters; + +@AllArgsConstructor +public class LuckPermsSubjectData implements SubjectData { + private final boolean enduring; + private final LuckPermsSubject superClass; + private final LuckPermsService service; + + @Getter + private final PermissionHolder holder; + + @Override + public Map, Map> getAllPermissions() { + Map, Map> perms = new HashMap<>(); + + for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { + Set contexts = n.getExtraContexts().entrySet().stream() + .map(entry -> new Context(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()); + + if (n.isServerSpecific()) { + contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); + } + + if (n.isWorldSpecific()) { + contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); + } + + if (!perms.containsKey(contexts)) { + perms.put(contexts, new HashMap<>()); + } + + perms.get(contexts).put(n.getPermission(), n.getValue()); + } + + ImmutableMap.Builder, Map> map = ImmutableMap.builder(); + for (Map.Entry, Map> e : perms.entrySet()) { + map.put(ImmutableSet.copyOf(e.getKey()), ImmutableMap.copyOf(e.getValue())); + } + return map.build(); + } + + @Override + public Map getPermissions(Set set) { + return getAllPermissions().getOrDefault(set, ImmutableMap.of()); + } + + @Override + public boolean setPermission(Set set, String s, Tristate tristate) { + if (tristate == Tristate.UNDEFINED) { + // Unset + Node.Builder builder = new me.lucko.luckperms.core.Node.Builder(s); + + for (Context ct : set) { + builder.withExtraContext(ct.getKey(), ct.getValue()); + } + + try { + if (enduring) { + holder.unsetPermission(builder.build()); + } else { + holder.unsetTransientPermission(builder.build()); + } + } catch (ObjectLacksException ignored) {} + superClass.objectSave(holder); + return true; + } + + Node.Builder builder = new me.lucko.luckperms.core.Node.Builder(s) + .setValue(tristate.asBoolean()); + + for (Context ct : set) { + builder.withExtraContext(ct.getKey(), ct.getValue()); + } + + try { + if (enduring) { + holder.setPermission(builder.build()); + } else { + holder.setTransientPermission(builder.build()); + } + } catch (ObjectAlreadyHasException ignored) {} + superClass.objectSave(holder); + return true; + } + + @Override + public boolean clearPermissions() { + if (enduring) { + holder.clearNodes(); + } else { + holder.clearTransientNodes(); + } + superClass.objectSave(holder); + return true; + } + + @Override + public boolean clearPermissions(Set set) { + Map context = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() + .filter(node -> node.shouldApplyWithContext(context)) + .collect(Collectors.toList()); + + toRemove.forEach(n -> { + try { + if (enduring) { + holder.unsetPermission(n); + } else { + holder.unsetTransientPermission(n); + } + } catch (ObjectLacksException ignored) {} + }); + + if (holder instanceof User) { + service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); + } + + superClass.objectSave(holder); + return !toRemove.isEmpty(); + } + + @Override + public Map, List> getAllParents() { + Map, List> parents = new HashMap<>(); + + for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { + if (!n.isGroupNode()) { + continue; + } + + Set contexts = n.getExtraContexts().entrySet().stream() + .map(entry -> new Context(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()); + + if (n.isServerSpecific()) { + contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); + } + + if (n.isWorldSpecific()) { + contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); + } + + if (!parents.containsKey(contexts)) { + parents.put(contexts, new ArrayList<>()); + } + + parents.get(contexts).add(service.getGroupSubjects().get(n.getGroupName())); + } + + ImmutableMap.Builder, List> map = ImmutableMap.builder(); + for (Map.Entry, List> e : parents.entrySet()) { + map.put(ImmutableSet.copyOf(e.getKey()), ImmutableList.copyOf(e.getValue())); + } + return map.build(); + } + + @Override + public List getParents(Set contexts) { + return getAllParents().getOrDefault(contexts, ImmutableList.of()); + } + + @Override + public boolean addParent(Set set, Subject subject) { + if (subject instanceof LuckPermsSubject) { + LuckPermsSubject permsSubject = ((LuckPermsSubject) subject); + Map contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + + try { + if (enduring) { + holder.setPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) + .withExtraContext(contexts) + .build()); + } else { + holder.setTransientPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) + .withExtraContext(contexts) + .build()); + } + } catch (ObjectAlreadyHasException ignored) {} + superClass.objectSave(holder); + } else { + return false; + } + return false; + } + + @Override + public boolean removeParent(Set set, Subject subject) { + if (subject instanceof LuckPermsSubject) { + LuckPermsSubject permsSubject = ((LuckPermsSubject) subject); + Map contexts = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + + try { + if (enduring) { + holder.unsetPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) + .withExtraContext(contexts) + .build()); + } else { + holder.unsetTransientPermission(new me.lucko.luckperms.core.Node.Builder("group." + permsSubject.getIdentifier()) + .withExtraContext(contexts) + .build()); + } + } catch (ObjectLacksException ignored) {} + superClass.objectSave(holder); + } else { + return false; + } + return false; + } + + @Override + public boolean clearParents() { + List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() + .filter(Node::isGroupNode) + .collect(Collectors.toList()); + + toRemove.forEach(n -> { + try { + if (enduring) { + holder.unsetPermission(n); + } else { + holder.unsetTransientPermission(n); + } + } catch (ObjectLacksException ignored) {} + }); + + if (holder instanceof User) { + service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); + } + + superClass.objectSave(holder); + return !toRemove.isEmpty(); + } + + @Override + public boolean clearParents(Set set) { + Map context = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() + .filter(Node::isGroupNode) + .filter(node -> node.shouldApplyWithContext(context)) + .collect(Collectors.toList()); + + toRemove.forEach(n -> { + try { + if (enduring) { + holder.unsetPermission(n); + } else { + holder.unsetTransientPermission(n); + } + } catch (ObjectLacksException ignored) {} + }); + + if (holder instanceof User) { + service.getPlugin().getUserManager().giveDefaultIfNeeded(((User) holder), false); + } + + superClass.objectSave(holder); + return !toRemove.isEmpty(); + } + + @Override + public Map, Map> getAllOptions() { + Map, Map> options = new HashMap<>(); + + for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { + if (!n.isMeta() && !n.isPrefix() && !n.isSuffix()) { + continue; + } + + Set contexts = n.getExtraContexts().entrySet().stream() + .map(entry -> new Context(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()); + + if (n.isServerSpecific()) { + contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); + } + + if (n.isWorldSpecific()) { + contexts.add(new Context(Context.WORLD_KEY, n.getWorld().get())); + } + + if (!options.containsKey(contexts)) { + options.put(contexts, new HashMap<>()); + } + + options.get(contexts).put(unescapeCharacters(n.getMeta().getKey()), unescapeCharacters(n.getMeta().getValue())); + } + + ImmutableMap.Builder, Map> map = ImmutableMap.builder(); + for (Map.Entry, Map> e : options.entrySet()) { + map.put(ImmutableSet.copyOf(e.getKey()), ImmutableMap.copyOf(e.getValue())); + } + return map.build(); + } + + @Override + public Map getOptions(Set set) { + return getAllOptions().getOrDefault(set, Collections.emptyMap()); + } + + @Override + public boolean setOption(Set set, String key, String value) { + Map context = new HashMap<>(); + for (Context c : set) { + context.put(c.getKey(), c.getValue()); + } + + key = escapeCharacters(key); + value = escapeCharacters(value); + + try { + if (enduring) { + holder.setPermission(new me.lucko.luckperms.core.Node.Builder("meta." + key + "." + value) + .withExtraContext(context) + .build() + ); + } else { + holder.setTransientPermission(new me.lucko.luckperms.core.Node.Builder("meta." + key + "." + value) + .withExtraContext(context) + .build() + ); + } + } catch (ObjectAlreadyHasException ignored) {} + superClass.objectSave(holder); + return true; + } + + @Override + public boolean clearOptions(Set set) { + Map context = set.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() + .filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix()) + .filter(node -> node.shouldApplyWithContext(context)) + .collect(Collectors.toList()); + + toRemove.forEach(n -> { + try { + if (enduring) { + holder.unsetPermission(n); + } else { + holder.unsetTransientPermission(n); + } + } catch (ObjectLacksException ignored) {} + }); + + superClass.objectSave(holder); + return !toRemove.isEmpty(); + } + + @Override + public boolean clearOptions() { + List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() + .filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix()) + .collect(Collectors.toList()); + + toRemove.forEach(n -> { + try { + if (enduring) { + holder.unsetPermission(n); + } else { + holder.unsetTransientPermission(n); + } + } catch (ObjectLacksException ignored) {} + }); + + superClass.objectSave(holder); + return !toRemove.isEmpty(); + } +}